Merge remote-tracking branch 'marmarek/master' into core3-devel

The following list is bollocks. There were many, many more.

Conflicts:
    core-modules/003QubesTemplateVm.py
    core-modules/005QubesNetVm.py
    core/qubes.py
    core/storage/__init__.py
    core/storage/xen.py
    doc/qvm-tools/qvm-pci.rst
    doc/qvm-tools/qvm-prefs.rst
    qubes/tools/qmemmand.py
    qvm-tools/qvm-create
    qvm-tools/qvm-prefs
    qvm-tools/qvm-start
    tests/__init__.py
    vm-config/xen-vm-template-hvm.xml

This commit took 2 days (26-27.01.2016) and put our friendship to test.
    --Wojtek and Marek
This commit is contained in:
Wojtek Porczyk 2016-03-03 01:05:23 +01:00
commit 9eafa57539
70 changed files with 4548 additions and 994 deletions

View File

@ -59,9 +59,10 @@ endif
python setup.py install -O1 --skip-build --root $(DESTDIR) python setup.py install -O1 --skip-build --root $(DESTDIR)
# $(MAKE) install -C tests # $(MAKE) install -C tests
$(MAKE) install -C relaxng $(MAKE) install -C relaxng
mkdir -p $(DESTDIR)/etc/qubes
cp etc/storage.conf $(DESTDIR)/etc/qubes/
ifeq ($(BACKEND_VMM),xen) ifeq ($(BACKEND_VMM),xen)
# Currently supported only on xen # Currently supported only on xen
mkdir -p $(DESTDIR)/etc/qubes
cp etc/qmemman.conf $(DESTDIR)/etc/qubes/ cp etc/qmemman.conf $(DESTDIR)/etc/qubes/
endif endif
$(MAKE) install -C dispvm $(MAKE) install -C dispvm

View File

@ -23,6 +23,8 @@
# #
import datetime import datetime
import base64
import hashlib
import logging import logging
import lxml.etree import lxml.etree
import os import os
@ -34,15 +36,17 @@ import sys
import time import time
import uuid import uuid
import xml.parsers.expat import xml.parsers.expat
import signal
from qubes import qmemman from qubes import qmemman
from qubes import qmemman_algo from qubes import qmemman_algo
import libvirt import libvirt
import warnings
from qubes.qubes import dry_run,vmm from qubes.qubes import dry_run,vmm
from qubes.qubes import register_qubes_vm_class from qubes.qubes import register_qubes_vm_class
from qubes.qubes import QubesVmCollection,QubesException,QubesHost,QubesVmLabels from qubes.qubes import QubesVmCollection,QubesException,QubesHost,QubesVmLabels
from qubes.qubes import defaults,system_path,vm_files,qubes_max_qid from qubes.qubes import defaults,system_path,vm_files,qubes_max_qid
from qubes.storage import get_pool
qmemman_present = False qmemman_present = False
try: try:
from qubes.qmemman_client import QMemmanClient from qubes.qmemman_client import QMemmanClient
@ -105,6 +109,7 @@ class QubesVm(object):
"name": { "order": 1 }, "name": { "order": 1 },
"uuid": { "order": 0, "eval": 'uuid.UUID(value) if value else None' }, "uuid": { "order": 0, "eval": 'uuid.UUID(value) if value else None' },
"dir_path": { "default": None, "order": 2 }, "dir_path": { "default": None, "order": 2 },
"pool_name": { "default":"default" },
"conf_file": { "conf_file": {
"func": lambda value: self.absolute_path(value, self.name + "func": lambda value: self.absolute_path(value, self.name +
".conf"), ".conf"),
@ -145,6 +150,7 @@ class QubesVm(object):
"order": 31, "order": 31,
"func": lambda value: value if not self.uses_default_kernelopts\ "func": lambda value: value if not self.uses_default_kernelopts\
else defaults["kernelopts_pcidevs"] if len(self.pcidevs)>0 \ else defaults["kernelopts_pcidevs"] if len(self.pcidevs)>0 \
else self.template.kernelopts if self.template
else defaults["kernelopts"] }, else defaults["kernelopts"] },
"mac": { "attr": "_mac", "default": None }, "mac": { "attr": "_mac", "default": None },
"include_in_backups": { "include_in_backups": {
@ -193,10 +199,10 @@ class QubesVm(object):
'kernelopts', 'services', 'installed_by_rpm',\ 'kernelopts', 'services', 'installed_by_rpm',\
'uses_default_netvm', 'include_in_backups', 'debug',\ 'uses_default_netvm', 'include_in_backups', 'debug',\
'qrexec_timeout', 'autostart', 'uses_default_dispvm_netvm', 'qrexec_timeout', 'autostart', 'uses_default_dispvm_netvm',
'backup_content', 'backup_size', 'backup_path' ]: 'backup_content', 'backup_size', 'backup_path', 'pool_name' ]:
attrs[prop]['save'] = lambda prop=prop: str(getattr(self, prop)) attrs[prop]['save'] = lambda prop=prop: str(getattr(self, prop))
# Simple paths # Simple paths
for prop in ['conf_file']: for prop in ['conf_file', 'firewall_conf']:
attrs[prop]['save'] = \ attrs[prop]['save'] = \
lambda prop=prop: self.relative_path(getattr(self, prop)) lambda prop=prop: self.relative_path(getattr(self, prop))
attrs[prop]['save_skip'] = \ attrs[prop]['save_skip'] = \
@ -334,12 +340,23 @@ class QubesVm(object):
if len(self.pcidevs) > 0: if len(self.pcidevs) > 0:
self.services['meminfo-writer'] = False self.services['meminfo-writer'] = False
if 'xml_element' not in kwargs:
# New VM, disable updates check if requested for new VMs
if os.path.exists(qubes.qubesutils.UPDATES_DEFAULT_VM_DISABLE_FLAG):
self.services['qubes-update-check'] = False
# Initialize VM image storage class # Initialize VM image storage class
self.storage = defaults["storage_class"](self) self.storage = get_pool(self.pool_name, self).getStorage()
self.dir_path = self.storage.vmdir
self.icon_path = os.path.join(self.storage.vmdir, 'icon.png')
self.conf_file = os.path.join(self.storage.vmdir, self.name + '.conf')
if hasattr(self, 'kernels_dir'): if hasattr(self, 'kernels_dir'):
self.storage.modules_img = os.path.join(self.kernels_dir, modules_path = os.path.join(self.kernels_dir,
"modules.img") "modules.img")
self.storage.modules_img_rw = self.kernel is None if os.path.exists(modules_path):
self.storage.modules_img = modules_path
self.storage.modules_img_rw = self.kernel is None
# Some additional checks for template based VM # Some additional checks for template based VM
if self.template is not None: if self.template is not None:
@ -369,8 +386,13 @@ class QubesVm(object):
def absolute_path(self, arg, default): def absolute_path(self, arg, default):
if arg is not None and os.path.isabs(arg): if arg is not None and os.path.isabs(arg):
return arg return arg
else: elif self.dir_path is not None:
return os.path.join(self.dir_path, (arg if arg is not None else default)) return os.path.join(self.dir_path, (arg if arg is not None else default))
else:
# cannot provide any meaningful value without dir_path; this is
# only to import some older format of `qubes.xml` (for example
# during migration from older release)
return None
def _absolute_path_gen(self, default): def _absolute_path_gen(self, default):
return lambda value: self.absolute_path(value, default) return lambda value: self.absolute_path(value, default)
@ -502,13 +524,14 @@ class QubesVm(object):
if not os.path.exists(os.path.join(system_path[ if not os.path.exists(os.path.join(system_path[
'qubes_kernels_base_dir'], new_value)): 'qubes_kernels_base_dir'], new_value)):
raise QubesException("Kernel '%s' not installed" % new_value) raise QubesException("Kernel '%s' not installed" % new_value)
for f in ('vmlinuz', 'modules.img'): for f in ('vmlinuz', 'initramfs'):
if not os.path.exists(os.path.join( if not os.path.exists(os.path.join(
system_path['qubes_kernels_base_dir'], new_value, f)): system_path['qubes_kernels_base_dir'], new_value, f)):
raise QubesException( raise QubesException(
"Kernel '%s' not properly installed: missing %s " "Kernel '%s' not properly installed: missing %s "
"file" % (new_value, f)) "file" % (new_value, f))
self._kernel = new_value self._kernel = new_value
self.uses_default_kernel = False
@property @property
def updateable(self): def updateable(self):
@ -543,9 +566,16 @@ class QubesVm(object):
return False return False
if len(name) > 31: if len(name) > 31:
return False return False
if name == 'lost+found':
# avoid conflict when /var/lib/qubes/appvms is mounted on
# separate partition
return False
return re.match(r"^[a-zA-Z][a-zA-Z0-9_.-]*$", name) is not None return re.match(r"^[a-zA-Z][a-zA-Z0-9_.-]*$", name) is not None
def pre_rename(self, new_name): def pre_rename(self, new_name):
if self.autostart:
subprocess.check_call(['sudo', 'systemctl', '-q', 'disable',
'qubes-vm@{}.service'.format(self.name)])
# fire hooks # fire hooks
for hook in self.hooks_pre_rename: for hook in self.hooks_pre_rename:
hook(self, new_name) hook(self, new_name)
@ -583,11 +613,17 @@ class QubesVm(object):
self.icon_path = self.icon_path.replace(old_dirpath, new_dirpath) self.icon_path = self.icon_path.replace(old_dirpath, new_dirpath)
if hasattr(self, 'kernels_dir') and self.kernels_dir is not None: if hasattr(self, 'kernels_dir') and self.kernels_dir is not None:
self.kernels_dir = self.kernels_dir.replace(old_dirpath, new_dirpath) self.kernels_dir = self.kernels_dir.replace(old_dirpath, new_dirpath)
if self.firewall_conf is not None:
self.firewall_conf = self.firewall_conf.replace(old_dirpath,
new_dirpath)
self._update_libvirt_domain() self._update_libvirt_domain()
self.post_rename(old_name) self.post_rename(old_name)
def post_rename(self, old_name): def post_rename(self, old_name):
if self.autostart:
# force setter to be called again
self.autostart = self.autostart
# fire hooks # fire hooks
for hook in self.hooks_post_rename: for hook in self.hooks_post_rename:
hook(self, old_name) hook(self, old_name)
@ -684,9 +720,11 @@ class QubesVm(object):
try: try:
return self.libvirt_domain.ID() return self.libvirt_domain.ID()
except libvirt.libvirtError as e: except libvirt.libvirtError as e:
if e.err[0] == libvirt.VIR_ERR_NO_DOMAIN: if e.get_error_code() == libvirt.VIR_ERR_NO_DOMAIN:
return -1 return -1
else: else:
print >>sys.stderr, "libvirt error code: {!r}".format(
e.get_error_code())
raise raise
@ -696,7 +734,17 @@ class QubesVm(object):
def _update_libvirt_domain(self): def _update_libvirt_domain(self):
domain_config = self.create_config_file() domain_config = self.create_config_file()
self._libvirt_domain = vmm.libvirt_conn.defineXML(domain_config) try:
self._libvirt_domain = vmm.libvirt_conn.defineXML(domain_config)
except libvirt.libvirtError as e:
# shouldn't this be in QubesHVm implementation?
if e.get_error_code() == libvirt.VIR_ERR_OS_TYPE and \
e.get_str2() == 'hvm':
raise QubesException("HVM domains not supported on this "
"machine. Check BIOS settings for "
"VT-x/AMD-V extensions.")
else:
raise e
self.uuid = uuid.UUID(bytes=self._libvirt_domain.UUID()) self.uuid = uuid.UUID(bytes=self._libvirt_domain.UUID())
@property @property
@ -726,9 +774,14 @@ class QubesVm(object):
return 0 return 0
return self.libvirt_domain.info()[1] return self.libvirt_domain.info()[1]
except libvirt.libvirtError as e: except libvirt.libvirtError as e:
if e.err[0] == libvirt.VIR_ERR_NO_DOMAIN: if e.get_error_code() == libvirt.VIR_ERR_NO_DOMAIN:
return 0
# libxl_domain_info failed - domain no longer exists
elif e.get_error_code() == libvirt.VIR_ERR_INTERNAL_ERROR:
return 0 return 0
else: else:
print >>sys.stderr, "libvirt error code: {!r}".format(
e.get_error_code())
raise raise
def get_cputime(self): def get_cputime(self):
@ -740,9 +793,14 @@ class QubesVm(object):
return 0 return 0
return self.libvirt_domain.info()[4] return self.libvirt_domain.info()[4]
except libvirt.libvirtError as e: except libvirt.libvirtError as e:
if e.err[0] == libvirt.VIR_ERR_NO_DOMAIN: if e.get_error_code() == libvirt.VIR_ERR_NO_DOMAIN:
return 0
# libxl_domain_info failed - domain no longer exists
elif e.get_error_code() == libvirt.VIR_INTERNAL_ERROR:
return 0 return 0
else: else:
print >>sys.stderr, "libvirt error code: {!r}".format(
e.get_error_code())
raise raise
def get_mem_static_max(self): def get_mem_static_max(self):
@ -752,7 +810,7 @@ class QubesVm(object):
try: try:
return self.libvirt_domain.maxMemory() return self.libvirt_domain.maxMemory()
except libvirt.libvirtError as e: except libvirt.libvirtError as e:
if e.err[0] == libvirt.VIR_ERR_NO_DOMAIN: if e.get_error_code() == libvirt.VIR_ERR_NO_DOMAIN:
return 0 return 0
else: else:
raise raise
@ -781,9 +839,11 @@ class QubesVm(object):
else: else:
return 0 return 0
except libvirt.libvirtError as e: except libvirt.libvirtError as e:
if e.err[0] == libvirt.VIR_ERR_NO_DOMAIN: if e.get_error_code() == libvirt.VIR_ERR_NO_DOMAIN:
return 0 return 0
else: else:
print >>sys.stderr, "libvirt error code: {!r}".format(
e.get_error_code())
raise raise
def get_disk_utilization_root_img(self): def get_disk_utilization_root_img(self):
@ -820,7 +880,7 @@ class QubesVm(object):
else: else:
return 'Halted' return 'Halted'
except libvirt.libvirtError as e: except libvirt.libvirtError as e:
if e.err[0] == libvirt.VIR_ERR_NO_DOMAIN: if e.get_error_code() == libvirt.VIR_ERR_NO_DOMAIN:
return "Halted" return "Halted"
else: else:
raise raise
@ -848,15 +908,19 @@ class QubesVm(object):
return True return True
def is_running(self): def is_running(self):
if vmm.offline_mode:
return False
try: try:
if self.libvirt_domain.isActive(): if self.libvirt_domain.isActive():
return True return True
else: else:
return False return False
except libvirt.libvirtError as e: except libvirt.libvirtError as e:
if e.err[0] == libvirt.VIR_ERR_NO_DOMAIN: if e.get_error_code() == libvirt.VIR_ERR_NO_DOMAIN:
return False return False
else: else:
print >>sys.stderr, "libvirt error code: {!r}".format(
e.get_error_code())
raise raise
def is_paused(self): def is_paused(self):
@ -866,9 +930,11 @@ class QubesVm(object):
else: else:
return False return False
except libvirt.libvirtError as e: except libvirt.libvirtError as e:
if e.err[0] == libvirt.VIR_ERR_NO_DOMAIN: if e.get_error_code() == libvirt.VIR_ERR_NO_DOMAIN:
return False return False
else: else:
print >>sys.stderr, "libvirt error code: {!r}".format(
e.get_error_code())
raise raise
def get_start_time(self): def get_start_time(self):
@ -974,8 +1040,9 @@ class QubesVm(object):
return None return None
if tz_info.st_nlink > 1: if tz_info.st_nlink > 1:
p = subprocess.Popen(['find', '/usr/share/zoneinfo', p = subprocess.Popen(['find', '/usr/share/zoneinfo',
'-inum', str(tz_info.st_ino)], '-inum', str(tz_info.st_ino),
stdout=subprocess.PIPE) '-print', '-quit'],
stdout=subprocess.PIPE)
tz_path = p.communicate()[0].strip() tz_path = p.communicate()[0].strip()
return tz_path.replace('/usr/share/zoneinfo/', '') return tz_path.replace('/usr/share/zoneinfo/', '')
return None return None
@ -1008,6 +1075,10 @@ class QubesVm(object):
self.qdb.write("/name", self.name) self.qdb.write("/name", self.name)
self.qdb.write("/qubes-vm-type", self.type) self.qdb.write("/qubes-vm-type", self.type)
self.qdb.write("/qubes-vm-updateable", str(self.updateable)) self.qdb.write("/qubes-vm-updateable", str(self.updateable))
self.qdb.write("/qubes-vm-persistence",
"full" if self.updateable else "rw-only")
self.qdb.write("/qubes-base-template",
self.template.name if self.template else '')
if self.is_netvm(): if self.is_netvm():
self.qdb.write("/qubes-netvm-gateway", self.gateway) self.qdb.write("/qubes-netvm-gateway", self.gateway)
@ -1036,6 +1107,8 @@ class QubesVm(object):
self.qdb.write("/qubes-debug-mode", str(int(self.debug))) self.qdb.write("/qubes-debug-mode", str(int(self.debug)))
self.provide_random_seed_to_vm()
# TODO: Currently the whole qmemman is quite Xen-specific, so stay with # TODO: Currently the whole qmemman is quite Xen-specific, so stay with
# xenstore for it until decided otherwise # xenstore for it until decided otherwise
if qmemman_present: if qmemman_present:
@ -1046,6 +1119,14 @@ class QubesVm(object):
for hook in self.hooks_create_qubesdb_entries: for hook in self.hooks_create_qubesdb_entries:
hook(self) hook(self)
def provide_random_seed_to_vm(self):
f = open('/dev/urandom', 'r')
s = f.read(64)
if len(s) != 64:
raise IOError("failed to read seed from /dev/urandom")
f.close()
self.qdb.write("/qubes-random-seed", base64.b64encode(hashlib.sha512(s).digest()))
def _format_net_dev(self, ip, mac, backend): def _format_net_dev(self, ip, mac, backend):
template = " <interface type='ethernet'>\n" \ template = " <interface type='ethernet'>\n" \
" <mac address='{mac}'/>\n" \ " <mac address='{mac}'/>\n" \
@ -1096,8 +1177,10 @@ class QubesVm(object):
args['dns2'] = self.secondary_dns args['dns2'] = self.secondary_dns
args['netmask'] = self.netmask args['netmask'] = self.netmask
args['netdev'] = self._format_net_dev(self.ip, self.mac, self.netvm.name) args['netdev'] = self._format_net_dev(self.ip, self.mac, self.netvm.name)
args['disable_network1'] = ''; args['network_begin'] = ''
args['disable_network2'] = ''; args['network_end'] = ''
args['no_network_begin'] = '<!--'
args['no_network_end'] = '-->'
else: else:
args['ip'] = '' args['ip'] = ''
args['mac'] = '' args['mac'] = ''
@ -1106,8 +1189,10 @@ class QubesVm(object):
args['dns2'] = '' args['dns2'] = ''
args['netmask'] = '' args['netmask'] = ''
args['netdev'] = '' args['netdev'] = ''
args['disable_network1'] = '<!--'; args['network_begin'] = '<!--'
args['disable_network2'] = '-->'; args['network_end'] = '-->'
args['no_network_begin'] = ''
args['no_network_end'] = ''
args.update(self.storage.get_config_params()) args.update(self.storage.get_config_params())
if hasattr(self, 'kernelopts'): if hasattr(self, 'kernelopts'):
args['kernelopts'] = self.kernelopts args['kernelopts'] = self.kernelopts
@ -1192,16 +1277,20 @@ class QubesVm(object):
shutil.copy(self.label.icon_path, self.icon_path) shutil.copy(self.label.icon_path, self.icon_path)
# Make sure that we have UUID allocated # Make sure that we have UUID allocated
self._update_libvirt_domain() if not vmm.offline_mode:
self._update_libvirt_domain()
else:
self.uuid = uuid.uuid4()
# fire hooks # fire hooks
for hook in self.hooks_create_on_disk: for hook in self.hooks_create_on_disk:
hook(self, verbose, source_template=source_template) hook(self, verbose, source_template=source_template)
def get_clone_attrs(self): def get_clone_attrs(self):
attrs = ['kernel', 'uses_default_kernel', 'netvm', 'uses_default_netvm', \ attrs = ['kernel', 'uses_default_kernel', 'netvm', 'uses_default_netvm',
'memory', 'maxmem', 'kernelopts', 'uses_default_kernelopts', 'services', 'vcpus', \ 'memory', 'maxmem', 'kernelopts', 'uses_default_kernelopts',
'_mac', 'pcidevs', 'include_in_backups', '_label', 'default_user'] 'services', 'vcpus', '_mac', 'pcidevs', 'include_in_backups',
'_label', 'default_user', 'qrexec_timeout']
# fire hooks # fire hooks
for hook in self.hooks_get_clone_attrs: for hook in self.hooks_get_clone_attrs:
@ -1246,6 +1335,9 @@ class QubesVm(object):
print >> sys.stderr, "--> Copying icon: {0} -> {1}".format(src_vm.icon_path, self.icon_path) print >> sys.stderr, "--> Copying icon: {0} -> {1}".format(src_vm.icon_path, self.icon_path)
shutil.copy(src_vm.icon_path, self.icon_path) shutil.copy(src_vm.icon_path, self.icon_path)
if src_vm.has_firewall():
self.write_firewall_conf(src_vm.get_firewall_conf())
# Make sure that we have UUID allocated # Make sure that we have UUID allocated
self._update_libvirt_domain() self._update_libvirt_domain()
@ -1287,10 +1379,12 @@ class QubesVm(object):
try: try:
self.libvirt_domain.undefine() self.libvirt_domain.undefine()
except libvirt.libvirtError as e: except libvirt.libvirtError as e:
if e.err[0] == libvirt.VIR_ERR_NO_DOMAIN: if e.get_error_code() == libvirt.VIR_ERR_NO_DOMAIN:
# already undefined # already undefined
pass pass
else: else:
print >>sys.stderr, "libvirt error code: {!r}".format(
e.get_error_code())
raise raise
self.storage.remove_from_disk() self.storage.remove_from_disk()
@ -1527,7 +1621,7 @@ class QubesVm(object):
call_kwargs = {} call_kwargs = {}
if ignore_stderr or not passio: if ignore_stderr or not passio:
null = open("/dev/null", "rw") null = open("/dev/null", "w+")
call_kwargs['stderr'] = null call_kwargs['stderr'] = null
if not passio: if not passio:
call_kwargs['stdin'] = null call_kwargs['stdin'] = null
@ -1552,16 +1646,22 @@ class QubesVm(object):
return retcode return retcode
def run_service(self, service, source="dom0", user=None, def run_service(self, service, source="dom0", user=None,
passio_popen = False, input=None): passio_popen=False, input=None, localcmd=None, gui=False,
if input and passio_popen: wait=True):
raise ValueError("'input' and 'passio_popen' cannot be used " if bool(input) + bool(passio_popen) + bool(localcmd) > 1:
"together") raise ValueError("'input', 'passio_popen', 'localcmd' cannot be "
if input: "used together")
if localcmd:
return self.run("QUBESRPC %s %s" % (service, source), return self.run("QUBESRPC %s %s" % (service, source),
localcmd="echo %s" % input, user=user, wait=True) localcmd=localcmd, user=user, wait=wait, gui=gui)
elif input:
return self.run("QUBESRPC %s %s" % (service, source),
localcmd="echo %s" % input, user=user, wait=wait,
gui=gui)
else: else:
return self.run("QUBESRPC %s %s" % (service, source), return self.run("QUBESRPC %s %s" % (service, source),
passio_popen=passio_popen, user=user, wait=True) passio_popen=passio_popen, user=user, wait=wait,
gui=gui)
def attach_network(self, verbose = False, wait = True, netvm = None): def attach_network(self, verbose = False, wait = True, netvm = None):
self.log.debug('attach_network(netvm={!r})'.format(netvm)) self.log.debug('attach_network(netvm={!r})'.format(netvm))
@ -1646,7 +1746,10 @@ class QubesVm(object):
if verbose: if verbose:
print >> sys.stderr, "--> Sending monitor layout..." print >> sys.stderr, "--> Sending monitor layout..."
monitor_layout = qubes.monitorlayoutnotify.get_monitor_layout() monitor_layout = qubes.monitorlayoutnotify.get_monitor_layout()
qubes.monitorlayoutnotify.notify_vm(self, monitor_layout) # Notify VM only if we've got a monitor_layout which is not empty
# or else we break proper VM resolution set by gui-agent
if len(monitor_layout) > 0:
qubes.monitorlayoutnotify.notify_vm(self, monitor_layout)
except ImportError as e: except ImportError as e:
print >>sys.stderr, "ERROR: %s" % e print >>sys.stderr, "ERROR: %s" % e
@ -1671,14 +1774,56 @@ class QubesVm(object):
def start_qubesdb(self): def start_qubesdb(self):
self.log.debug('start_qubesdb()') self.log.debug('start_qubesdb()')
pidfile = '/var/run/qubes/qubesdb.{}.pid'.format(self.name)
try:
if os.path.exists(pidfile):
old_qubesdb_pid = open(pidfile, 'r').read()
try:
os.kill(int(old_qubesdb_pid), signal.SIGTERM)
except OSError:
raise QubesException(
"Failed to kill old QubesDB instance (PID {}). "
"Terminate it manually and retry. "
"If that isn't QubesDB process, "
"remove the pidfile: {}".format(old_qubesdb_pid,
pidfile))
timeout = 25
while os.path.exists(pidfile) and timeout:
time.sleep(0.2)
timeout -= 1
except IOError: # ENOENT (pidfile)
pass
# force connection to a new daemon
self._qdb_connection = None
retcode = subprocess.call ([ retcode = subprocess.call ([
system_path["qubesdb_daemon_path"], system_path["qubesdb_daemon_path"],
str(self.xid), str(self.xid),
self.name]) self.name])
if retcode != 0: if retcode != 0:
self.force_shutdown()
raise OSError("ERROR: Cannot execute qubesdb-daemon!") raise OSError("ERROR: Cannot execute qubesdb-daemon!")
def request_memory(self, mem_required = None):
# Overhead of per-VM/per-vcpu Xen structures, taken from OpenStack nova/virt/xenapi/driver.py
# see https://wiki.openstack.org/wiki/XenServer/Overhead
# add an extra MB because Nova rounds up to MBs
MEM_OVERHEAD_BASE = (3 + 1) * 1024 * 1024
MEM_OVERHEAD_PER_VCPU = 3 * 1024 * 1024 / 2
if mem_required is None:
mem_required = int(self.memory) * 1024 * 1024
if qmemman_present:
qmemman_client = QMemmanClient()
try:
mem_required_with_overhead = mem_required + MEM_OVERHEAD_BASE + self.vcpus * MEM_OVERHEAD_PER_VCPU
got_memory = qmemman_client.request_memory(mem_required_with_overhead)
except IOError as e:
raise IOError("ERROR: Failed to connect to qmemman: %s" % str(e))
if not got_memory:
qmemman_client.close()
raise MemoryError ("ERROR: insufficient memory to start VM '%s'" % self.name)
return qmemman_client
def start(self, verbose = False, preparing_dvm = False, start_guid = True, def start(self, verbose = False, preparing_dvm = False, start_guid = True,
notify_function = None, mem_required = None): notify_function = None, mem_required = None):
self.log.debug('start(' self.log.debug('start('
@ -1706,24 +1851,14 @@ class QubesVm(object):
self._update_libvirt_domain() self._update_libvirt_domain()
if mem_required is None: qmemman_client = self.request_memory(mem_required)
mem_required = int(self.memory) * 1024 * 1024
if qmemman_present:
qmemman_client = QMemmanClient()
try:
got_memory = qmemman_client.request_memory(mem_required)
except IOError as e:
raise IOError("ERROR: Failed to connect to qmemman: %s" % str(e))
if not got_memory:
qmemman_client.close()
raise MemoryError ("ERROR: insufficient memory to start VM '%s'" % self.name)
# Bind pci devices to pciback driver # Bind pci devices to pciback driver
for pci in self.pcidevs: for pci in self.pcidevs:
try: try:
nd = vmm.libvirt_conn.nodeDeviceLookupByName('pci_0000_' + pci.replace(':','_').replace('.','_')) nd = vmm.libvirt_conn.nodeDeviceLookupByName('pci_0000_' + pci.replace(':','_').replace('.','_'))
except libvirt.libvirtError as e: except libvirt.libvirtError as e:
if e.err[0] == libvirt.VIR_ERR_NO_NODE_DEVICE: if e.get_error_code() == libvirt.VIR_ERR_NO_NODE_DEVICE:
raise QubesException( raise QubesException(
"PCI device {} does not exist (domain {})". "PCI device {} does not exist (domain {})".
format(pci, self.name)) format(pci, self.name))
@ -1732,39 +1867,43 @@ class QubesVm(object):
try: try:
nd.dettach() nd.dettach()
except libvirt.libvirtError as e: except libvirt.libvirtError as e:
if e.err[0] == libvirt.VIR_ERR_INTERNAL_ERROR: if e.get_error_code() == libvirt.VIR_ERR_INTERNAL_ERROR:
# allready detached # already detached
pass pass
else: else:
raise raise
self.libvirt_domain.createWithFlags(libvirt.VIR_DOMAIN_START_PAUSED) self.libvirt_domain.createWithFlags(libvirt.VIR_DOMAIN_START_PAUSED)
if verbose: try:
print >> sys.stderr, "--> Starting Qubes DB..." if verbose:
self.start_qubesdb() print >> sys.stderr, "--> Starting Qubes DB..."
self.start_qubesdb()
xid = self.xid xid = self.xid
self.log.debug('xid={}'.format(xid)) self.log.debug('xid={}'.format(xid))
if preparing_dvm: if preparing_dvm:
self.services['qubes-dvm'] = True self.services['qubes-dvm'] = True
if verbose: if verbose:
print >> sys.stderr, "--> Setting Qubes DB info for the VM..." print >> sys.stderr, "--> Setting Qubes DB info for the VM..."
self.create_qubesdb_entries() self.create_qubesdb_entries()
if verbose: if verbose:
print >> sys.stderr, "--> Updating firewall rules..." print >> sys.stderr, "--> Updating firewall rules..."
netvm = self.netvm netvm = self.netvm
while netvm is not None: while netvm is not None:
if netvm.is_proxyvm() and netvm.is_running(): if netvm.is_proxyvm() and netvm.is_running():
netvm.write_iptables_qubesdb_entry() netvm.write_iptables_qubesdb_entry()
netvm = netvm.netvm netvm = netvm.netvm
# fire hooks # fire hooks
for hook in self.hooks_start: for hook in self.hooks_start:
hook(self, verbose = verbose, preparing_dvm = preparing_dvm, hook(self, verbose = verbose, preparing_dvm = preparing_dvm,
start_guid = start_guid, notify_function = notify_function) start_guid = start_guid, notify_function = notify_function)
except:
self.force_shutdown()
raise
if verbose: if verbose:
print >> sys.stderr, "--> Starting the VM..." print >> sys.stderr, "--> Starting the VM..."
@ -1828,6 +1967,15 @@ class QubesVm(object):
if not self.is_running(): if not self.is_running():
raise QubesException ("VM already stopped!") raise QubesException ("VM already stopped!")
# try to gracefully detach PCI devices before shutdown, to mitigate
# timeouts on forcible detach at domain destroy; if that fails, too bad
try:
for pcidev in self.pcidevs:
self.libvirt_domain.detachDevice(self._format_pci_dev(pcidev))
except libvirt.libvirtError as e:
print >>sys.stderr, "WARNING: {}, continuing VM shutdown " \
"anyway".format(str(e))
self.libvirt_domain.shutdown() self.libvirt_domain.shutdown()
def force_shutdown(self, xid = None): def force_shutdown(self, xid = None):

View File

@ -0,0 +1,77 @@
#!/usr/bin/python2
# -*- encoding: utf8 -*-
#
# The Qubes OS Project, http://www.qubes-os.org
#
# 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.
#
#
from __future__ import (
absolute_import,
division,
print_function,
unicode_literals,
)
from qubes.qubes import (
register_qubes_vm_class,
QubesException,
QubesVm,
)
from time import sleep
class QubesResizableVm(QubesVm):
def resize_root_img(self, size, allow_start=False):
if self.template:
raise QubesException("Cannot resize root.img of template-based VM"
". Resize the root.img of the template "
"instead.")
if self.is_running():
raise QubesException("Cannot resize root.img of running VM")
if size < self.get_root_img_sz():
raise QubesException(
"For your own safety shringing of root.img is disabled. If "
"you really know what you are doing, use 'truncate' manually.")
f_root = open(self.root_img, "a+b")
f_root.truncate(size)
f_root.close()
class QubesResizableVmWithResize2fs(QubesResizableVm):
def resize_root_img(self, size, allow_start=False):
super(QubesResizableVmWithResize2fs, self).\
resize_root_img(size, allow_start=allow_start)
if not allow_start:
raise QubesException("VM start required to complete the "
"operation, but not allowed. Either run the "
"operation again allowing VM start this "
"time, or run resize2fs in the VM manually.")
self.start(start_guid=False)
self.run("resize2fs /dev/mapper/dmroot", user="root", wait=True,
gui=False)
self.shutdown()
while self.is_running():
sleep(1)
register_qubes_vm_class(QubesResizableVm)
register_qubes_vm_class(QubesResizableVmWithResize2fs)

View File

@ -58,6 +58,8 @@ class QubesProxyVm(QubesNetVm):
def _set_netvm(self, new_netvm): def _set_netvm(self, new_netvm):
old_netvm = self.netvm old_netvm = self.netvm
super(QubesProxyVm, self)._set_netvm(new_netvm) super(QubesProxyVm, self)._set_netvm(new_netvm)
if vmm.offline_mode:
return
if self.netvm is not None: if self.netvm is not None:
self.netvm.add_external_ip_permission(self.get_xid()) self.netvm.add_external_ip_permission(self.get_xid())
self.write_netvm_domid_entry() self.write_netvm_domid_entry()

View File

@ -24,9 +24,15 @@
import os.path import os.path
from qubes.qubes import QubesVm,QubesVmLabel,register_qubes_vm_class,system_path from qubes.qubes import (
register_qubes_vm_class,
system_path,
QubesResizableVmWithResize2fs,
QubesVmLabel,
)
class QubesAppVm(QubesVm):
class QubesAppVm(QubesResizableVmWithResize2fs):
""" """
A class that represents an AppVM. A child of QubesVm. A class that represents an AppVM. A child of QubesVm.
""" """

View File

@ -97,6 +97,11 @@ class QubesDisposableVm(QubesVm):
disp_template = kwargs['disp_template'] disp_template = kwargs['disp_template']
kwargs['template'] = disp_template.template kwargs['template'] = disp_template.template
kwargs['dir_path'] = disp_template.dir_path kwargs['dir_path'] = disp_template.dir_path
kwargs['kernel'] = disp_template.kernel
kwargs['uses_default_kernel'] = disp_template.uses_default_kernel
kwargs['kernelopts'] = disp_template.kernelopts
kwargs['uses_default_kernelopts'] = \
disp_template.uses_default_kernelopts
super(QubesDisposableVm, self).__init__(**kwargs) super(QubesDisposableVm, self).__init__(**kwargs)
assert self.template is not None, "Missing template for DisposableVM!" assert self.template is not None, "Missing template for DisposableVM!"
@ -151,6 +156,7 @@ class QubesDisposableVm(QubesVm):
def create_qubesdb_entries(self): def create_qubesdb_entries(self):
super(QubesDisposableVm, self).create_qubesdb_entries() super(QubesDisposableVm, self).create_qubesdb_entries()
self.qdb.write("/qubes-vm-persistence", "none")
self.qdb.write('/qubes-restore-complete', '1') self.qdb.write('/qubes-restore-complete', '1')
def start(self, verbose = False, **kwargs): def start(self, verbose = False, **kwargs):
@ -162,8 +168,13 @@ class QubesDisposableVm(QubesVm):
if self.get_power_state() != "Halted": if self.get_power_state() != "Halted":
raise QubesException ("VM is already running!") raise QubesException ("VM is already running!")
# skip netvm state checking - calling VM have the same netvm, so it if self.netvm is not None:
# must be already running if self.netvm.qid != 0:
if not self.netvm.is_running():
if verbose:
print >> sys.stderr, "--> Starting NetVM {0}...".\
format(self.netvm.name)
self.netvm.start(verbose=verbose, **kwargs)
if verbose: if verbose:
print >> sys.stderr, "--> Loading the VM (type = {0})...".format(self.type) print >> sys.stderr, "--> Loading the VM (type = {0})...".format(self.type)
@ -172,17 +183,7 @@ class QubesDisposableVm(QubesVm):
# refresh config file # refresh config file
domain_config = self.create_config_file() domain_config = self.create_config_file()
if qmemman_present: qmemman_client = self.request_memory()
mem_required = int(self.memory) * 1024 * 1024
print >>sys.stderr, "time=%s, getting %d memory" % (str(time.time()), mem_required)
qmemman_client = QMemmanClient()
try:
got_memory = qmemman_client.request_memory(mem_required)
except IOError as e:
raise IOError("ERROR: Failed to connect to qmemman: %s" % str(e))
if not got_memory:
qmemman_client.close()
raise MemoryError ("ERROR: insufficient memory to start VM '%s'" % self.name)
# dispvm cannot have PCI devices # dispvm cannot have PCI devices
assert (len(self.pcidevs) == 0), "DispVM cannot have PCI devices" assert (len(self.pcidevs) == 0), "DispVM cannot have PCI devices"
@ -236,5 +237,9 @@ class QubesDisposableVm(QubesVm):
return self.xid return self.xid
def remove_from_disk(self):
# nothing to remove
pass
# register classes # register classes
register_qubes_vm_class(QubesDisposableVm) register_qubes_vm_class(QubesDisposableVm)

View File

@ -26,16 +26,20 @@ import os
import os.path import os.path
import signal import signal
import subprocess import subprocess
import stat
import sys import sys
import re
import shutil import shutil
import stat
from xml.etree import ElementTree from xml.etree import ElementTree
from qubes.qubes import QubesVm,register_qubes_vm_class,vmm,dry_run from qubes.qubes import (
from qubes.qubes import system_path,defaults dry_run,
from qubes.qubes import QubesException defaults,
register_qubes_vm_class,
system_path,
vmm,
QubesException,
QubesResizableVm,
)
system_path["config_template_hvm"] = '/usr/share/qubes/vm-template-hvm.xml' system_path["config_template_hvm"] = '/usr/share/qubes/vm-template-hvm.xml'
@ -44,7 +48,7 @@ defaults["hvm_private_img_size"] = 2*1024*1024*1024
defaults["hvm_memory"] = 512 defaults["hvm_memory"] = 512
class QubesHVm(QubesVm): class QubesHVm(QubesResizableVm):
""" """
A class that represents an HVM. A child of QubesVm. A class that represents an HVM. A child of QubesVm.
""" """
@ -96,8 +100,6 @@ class QubesHVm(QubesVm):
(not 'xml_element' in kwargs or kwargs['xml_element'].get('guiagent_installed') is None): (not 'xml_element' in kwargs or kwargs['xml_element'].get('guiagent_installed') is None):
self.services['meminfo-writer'] = False self.services['meminfo-writer'] = False
self.storage.volatile_img = None
@property @property
def type(self): def type(self):
return "HVM" return "HVM"
@ -233,35 +235,7 @@ class QubesHVm(QubesVm):
if self.is_running(): if self.is_running():
raise NotImplementedError("Online resize of HVM's private.img not implemented, shutdown the VM first") raise NotImplementedError("Online resize of HVM's private.img not implemented, shutdown the VM first")
f_private = open (self.private_img, "a+b") self.storage.resize_private_img(size)
f_private.truncate (size)
f_private.close ()
def resize_root_img(self, size):
if self.template:
raise QubesException("Cannot resize root.img of template-based VM"
". Resize the root.img of the template "
"instead.")
if self.is_running():
raise QubesException("Cannot resize root.img of running HVM")
if size < self.get_root_img_sz():
raise QubesException(
"For your own safety shringing of root.img is disabled. If "
"you really know what you are doing, use 'truncate' manually.")
f_root = open (self.root_img, "a+b")
f_root.truncate (size)
f_root.close ()
def get_rootdev(self, source_template=None):
if self.template:
return "'script:snapshot:{template_root}:{volatile},xvda,w',".format(
template_root=self.template.root_img,
volatile=self.volatile_img)
else:
return "'script:file:{root_img},xvda,w',".format(root_img=self.root_img)
def get_config_params(self): def get_config_params(self):
@ -272,8 +246,8 @@ class QubesHVm(QubesVm):
params['volatiledev'] = '' params['volatiledev'] = ''
if self.timezone.lower() == 'localtime': if self.timezone.lower() == 'localtime':
params['time_basis'] = 'localtime' params['time_basis'] = 'localtime'
params['timeoffset'] = '0' params['timeoffset'] = '0'
elif self.timezone.isdigit(): elif self.timezone.isdigit():
params['time_basis'] = 'UTC' params['time_basis'] = 'UTC'
params['timeoffset'] = self.timezone params['timeoffset'] = self.timezone
@ -295,34 +269,6 @@ class QubesHVm(QubesVm):
return True return True
def reset_volatile_storage(self, **kwargs):
assert not self.is_running(), "Attempt to clean volatile image of running VM!"
source_template = kwargs.get("source_template", self.template)
if source_template is None:
# Nothing to do on non-template based VM
return
if os.path.exists (self.volatile_img):
if self.debug:
if os.path.getmtime(self.template.root_img) > os.path.getmtime(self.volatile_img):
if kwargs.get("verbose", False):
print >>sys.stderr, "--> WARNING: template have changed, resetting root.img"
else:
if kwargs.get("verbose", False):
print >>sys.stderr, "--> Debug mode: not resetting root.img"
print >>sys.stderr, "--> Debug mode: if you want to force root.img reset, either update template VM, or remove volatile.img file"
return
os.remove (self.volatile_img)
f_volatile = open (self.volatile_img, "w")
f_root = open (self.template.root_img, "r")
f_root.seek(0, os.SEEK_END)
f_volatile.truncate (f_root.tell()) # make empty sparse file of the same size as root.img
f_volatile.close ()
f_root.close()
@property @property
def vif(self): def vif(self):
if self.xid < 0: if self.xid < 0:
@ -367,12 +313,16 @@ class QubesHVm(QubesVm):
return -1 return -1
def start(self, *args, **kwargs): def start(self, *args, **kwargs):
# make it available to storage.prepare_for_vm_startup, which is
# called before actually building VM libvirt configuration
self.storage.drive = self.drive
if self.template and self.template.is_running(): if self.template and self.template.is_running():
raise QubesException("Cannot start the HVM while its template is running") raise QubesException("Cannot start the HVM while its template is running")
try: try:
if 'mem_required' not in kwargs: if 'mem_required' not in kwargs:
# Reserve 32MB for stubdomain # Reserve 44MB for stubdomain
kwargs['mem_required'] = (self.memory + 32) * 1024 * 1024 kwargs['mem_required'] = (self.memory + 44) * 1024 * 1024
return super(QubesHVm, self).start(*args, **kwargs) return super(QubesHVm, self).start(*args, **kwargs)
except QubesException as e: except QubesException as e:
capabilities = vmm.libvirt_conn.getCapabilities() capabilities = vmm.libvirt_conn.getCapabilities()
@ -400,25 +350,28 @@ class QubesHVm(QubesVm):
if (retcode != 0) : if (retcode != 0) :
raise QubesException("Cannot start qubes-guid!") raise QubesException("Cannot start qubes-guid!")
def start_guid(self, verbose = True, notify_function = None, def start_guid(self, verbose=True, notify_function=None,
before_qrexec=False, **kwargs): before_qrexec=False, **kwargs):
# If user force the guiagent, start_guid will mimic a standard QubesVM if not before_qrexec:
if not before_qrexec and self.guiagent_installed: return
kwargs['extra_guid_args'] = kwargs.get('extra_guid_args', []) + \
['-Q'] if not self.guiagent_installed or self.debug:
super(QubesHVm, self).start_guid(verbose, notify_function, **kwargs)
stubdom_guid_pidfile = '/var/run/qubes/guid-running.%d' % self.stubdom_xid
if os.path.exists(stubdom_guid_pidfile) and not self.debug:
try:
stubdom_guid_pid = int(open(stubdom_guid_pidfile, 'r').read())
os.kill(stubdom_guid_pid, signal.SIGTERM)
except Exception as ex:
print >> sys.stderr, "WARNING: Failed to kill stubdom gui daemon: %s" % str(ex)
elif before_qrexec and (not self.guiagent_installed or self.debug):
if verbose: if verbose:
print >> sys.stderr, "--> Starting Qubes GUId (full screen)..." print >> sys.stderr, "--> Starting Qubes GUId (full screen)..."
self.start_stubdom_guid(verbose=verbose) self.start_stubdom_guid(verbose=verbose)
kwargs['extra_guid_args'] = kwargs.get('extra_guid_args', []) + \
['-Q', '-n']
stubdom_guid_pidfile = \
'/var/run/qubes/guid-running.%d' % self.stubdom_xid
if not self.debug and os.path.exists(stubdom_guid_pidfile):
# Terminate stubdom guid once "real" gui agent connects
stubdom_guid_pid = int(open(stubdom_guid_pidfile, 'r').read())
kwargs['extra_guid_args'] += ['-K', str(stubdom_guid_pid)]
super(QubesHVm, self).start_guid(verbose, notify_function, **kwargs)
def start_qrexec_daemon(self, **kwargs): def start_qrexec_daemon(self, **kwargs):
if not self.qrexec_installed: if not self.qrexec_installed:
if kwargs.get('verbose', False): if kwargs.get('verbose', False):
@ -463,7 +416,6 @@ class QubesHVm(QubesVm):
guid_pid = open(guid_pidfile).read().strip() guid_pid = open(guid_pidfile).read().strip()
os.kill(int(guid_pid), 15) os.kill(int(guid_pid), 15)
def suspend(self): def suspend(self):
if dry_run: if dry_run:
return return

View File

@ -29,7 +29,7 @@ import stat
import sys import sys
import re import re
from qubes.qubes import QubesHVm,register_qubes_vm_class,dry_run from qubes.qubes import QubesHVm,register_qubes_vm_class,dry_run,vmm
from qubes.qubes import QubesException,QubesVmCollection from qubes.qubes import QubesException,QubesVmCollection
from qubes.qubes import system_path,defaults from qubes.qubes import system_path,defaults
@ -70,6 +70,7 @@ class QubesTemplateHVm(QubesHVm):
def is_appvm(self): def is_appvm(self):
return False return False
@property
def rootcow_img(self): def rootcow_img(self):
return self.storage.rootcow_img return self.storage.rootcow_img
@ -95,7 +96,15 @@ class QubesTemplateHVm(QubesHVm):
def commit_changes (self, verbose = False): def commit_changes (self, verbose = False):
self.log.debug('commit_changes()') self.log.debug('commit_changes()')
# nothing to do as long as root-cow.img is unused if not vmm.offline_mode:
pass assert not self.is_running(), "Attempt to commit changes on running Template VM!"
if verbose:
print >> sys.stderr, "--> Commiting template updates... COW: {0}...".format (self.rootcow_img)
if dry_run:
return
self.storage.commit_template_changes()
register_qubes_vm_class(QubesTemplateHVm) register_qubes_vm_class(QubesTemplateHVm)

View File

@ -400,6 +400,10 @@ class SendWorker(Process):
stdin=subprocess.PIPE, stdin=subprocess.PIPE,
stdout=self.backup_stdout) stdout=self.backup_stdout)
if final_proc.wait() >= 2: if final_proc.wait() >= 2:
if self.queue.full():
# if queue is already full, remove some entry to wake up
# main thread, so it will be able to notice error
self.queue.get()
# handle only exit code 2 (tar fatal error) or # handle only exit code 2 (tar fatal error) or
# greater (call failed?) # greater (call failed?)
raise QubesException( raise QubesException(
@ -448,6 +452,17 @@ def backup_do(base_backup_dir, files_to_backup, passphrase,
crypto_algorithm=DEFAULT_CRYPTO_ALGORITHM): crypto_algorithm=DEFAULT_CRYPTO_ALGORITHM):
global running_backup_operation global running_backup_operation
def queue_put_with_check(proc, vmproc, queue, element):
if queue.full():
if not proc.is_alive():
if vmproc:
message = ("Failed to write the backup, VM output:\n" +
vmproc.stderr.read())
else:
message = "Failed to write the backup. Out of disk space?"
raise QubesException(message)
queue.put(element)
total_backup_sz = 0 total_backup_sz = 0
passphrase = passphrase.encode('utf-8') passphrase = passphrase.encode('utf-8')
for f in files_to_backup: for f in files_to_backup:
@ -552,14 +567,16 @@ def backup_do(base_backup_dir, files_to_backup, passphrase,
# be verified before untaring this. # be verified before untaring this.
# Prefix the path in archive with filename["subdir"] to have it # Prefix the path in archive with filename["subdir"] to have it
# verified during untar # verified during untar
tar_cmdline = ["tar", "-Pc", '--sparse', tar_cmdline = (["tar", "-Pc", '--sparse',
"-f", backup_pipe, "-f", backup_pipe,
'-C', os.path.dirname(filename["path"]), '-C', os.path.dirname(filename["path"])] +
'--xform', 's:^%s:%s\\0:' % ( (['--dereference'] if filename["subdir"] != "dom0-home/"
else []) +
['--xform', 's:^%s:%s\\0:' % (
os.path.basename(filename["path"]), os.path.basename(filename["path"]),
filename["subdir"]), filename["subdir"]),
os.path.basename(filename["path"]) os.path.basename(filename["path"])
] ])
if compressed: if compressed:
tar_cmdline.insert(-1, tar_cmdline.insert(-1,
"--use-compress-program=%s" % compression_filter) "--use-compress-program=%s" % compression_filter)
@ -649,7 +666,9 @@ def backup_do(base_backup_dir, files_to_backup, passphrase,
run_error) run_error)
# Send the chunk to the backup target # Send the chunk to the backup target
to_send.put(os.path.relpath(chunkfile, backup_tmpdir)) queue_put_with_check(
send_proc, vmproc, to_send,
os.path.relpath(chunkfile, backup_tmpdir))
# Close HMAC # Close HMAC
hmac.stdin.close() hmac.stdin.close()
@ -667,7 +686,9 @@ def backup_do(base_backup_dir, files_to_backup, passphrase,
hmac_file.close() hmac_file.close()
# Send the HMAC to the backup target # Send the HMAC to the backup target
to_send.put(os.path.relpath(chunkfile, backup_tmpdir) + ".hmac") queue_put_with_check(
send_proc, vmproc, to_send,
os.path.relpath(chunkfile, backup_tmpdir) + ".hmac")
if tar_sparse.poll() is None or run_error == "size_limit": if tar_sparse.poll() is None or run_error == "size_limit":
run_error = "paused" run_error = "paused"
@ -679,7 +700,7 @@ def backup_do(base_backup_dir, files_to_backup, passphrase,
.poll() .poll()
pipe.close() pipe.close()
to_send.put("FINISHED") queue_put_with_check(send_proc, vmproc, to_send, "FINISHED")
send_proc.join() send_proc.join()
shutil.rmtree(backup_tmpdir) shutil.rmtree(backup_tmpdir)
@ -1552,6 +1573,8 @@ def backup_restore_set_defaults(options):
options['ignore-username-mismatch'] = False options['ignore-username-mismatch'] = False
if 'verify-only' not in options: if 'verify-only' not in options:
options['verify-only'] = False options['verify-only'] = False
if 'rename-conflicting' not in options:
options['rename-conflicting'] = False
return options return options
@ -1619,6 +1642,22 @@ def backup_restore_header(source, passphrase,
return (restore_tmpdir, os.path.join(restore_tmpdir, "qubes.xml"), return (restore_tmpdir, os.path.join(restore_tmpdir, "qubes.xml"),
header_data) header_data)
def generate_new_name_for_conflicting_vm(orig_name, host_collection,
restore_info):
number = 1
if len(orig_name) > 29:
orig_name = orig_name[0:29]
new_name = orig_name
while (new_name in restore_info.keys() or
new_name in map(lambda x: x.get('rename_to', None),
restore_info.values()) or
host_collection.get_vm_by_name(new_name)):
new_name = str('{}{}'.format(orig_name, number))
number += 1
if number == 100:
# give up
return None
return new_name
def restore_info_verify(restore_info, host_collection): def restore_info_verify(restore_info, host_collection):
options = restore_info['$OPTIONS$'] options = restore_info['$OPTIONS$']
@ -1636,7 +1675,16 @@ def restore_info_verify(restore_info, host_collection):
vm_info.pop('already-exists', None) vm_info.pop('already-exists', None)
if not options['verify-only'] and \ if not options['verify-only'] and \
host_collection.get_vm_by_name(vm) is not None: host_collection.get_vm_by_name(vm) is not None:
vm_info['already-exists'] = True if options['rename-conflicting']:
new_name = generate_new_name_for_conflicting_vm(
vm, host_collection, restore_info
)
if new_name is not None:
vm_info['rename-to'] = new_name
else:
vm_info['already-exists'] = True
else:
vm_info['already-exists'] = True
# check template # check template
vm_info.pop('missing-template', None) vm_info.pop('missing-template', None)
@ -1683,6 +1731,22 @@ def restore_info_verify(restore_info, host_collection):
'already-exists', 'already-exists',
'excluded']]) 'excluded']])
# update references to renamed VMs:
for vm in restore_info.keys():
if vm in ['$OPTIONS$', 'dom0']:
continue
vm_info = restore_info[vm]
template_name = vm_info['template']
if (template_name in restore_info and
restore_info[template_name]['good-to-go'] and
'rename-to' in restore_info[template_name]):
vm_info['template'] = restore_info[template_name]['rename-to']
netvm_name = vm_info['netvm']
if (netvm_name in restore_info and
restore_info[netvm_name]['good-to-go'] and
'rename-to' in restore_info[netvm_name]):
vm_info['netvm'] = restore_info[netvm_name]['rename-to']
return restore_info return restore_info
@ -1706,6 +1770,10 @@ def backup_restore_prepare(backup_location, passphrase, options=None,
if check_vm.qid == 0: if check_vm.qid == 0:
return os.path.exists(os.path.join(backup_dir, 'dom0-home')) return os.path.exists(os.path.join(backup_dir, 'dom0-home'))
# DisposableVM
if check_vm.dir_path is None:
return False
backup_vm_dir_path = check_vm.dir_path.replace( backup_vm_dir_path = check_vm.dir_path.replace(
system_path["qubes_base_dir"], backup_dir) system_path["qubes_base_dir"], backup_dir)
@ -1951,8 +2019,11 @@ def backup_restore_print_summary(restore_info, print_callback=print_stdout):
s += " <-- No matching template on the host or in the backup found!" s += " <-- No matching template on the host or in the backup found!"
elif 'missing-netvm' in vm_info: elif 'missing-netvm' in vm_info:
s += " <-- No matching netvm on the host or in the backup found!" s += " <-- No matching netvm on the host or in the backup found!"
elif 'orig-template' in vm_info: else:
s += " <-- Original template was '%s'" % (vm_info['orig-template']) if 'orig-template' in vm_info:
s += " <-- Original template was '%s'" % (vm_info['orig-template'])
if 'rename-to' in vm_info:
s += " <-- Will be renamed to '%s'" % vm_info['rename-to']
print_callback(s) print_callback(s)
@ -1987,7 +2058,7 @@ def backup_restore_do(restore_info,
backup_dir) backup_dir)
# We prefer to use Linux's cp, because it nicely handles sparse files # We prefer to use Linux's cp, because it nicely handles sparse files
cp_retcode = subprocess.call(["cp", "-rp", backup_src_dir, dst_dir]) cp_retcode = subprocess.call(["cp", "-rp", "--reflink=auto", backup_src_dir, dst_dir])
if cp_retcode != 0: if cp_retcode != 0:
raise QubesException( raise QubesException(
"*** Error while copying file {0} to {1}".format(backup_src_dir, "*** Error while copying file {0} to {1}".format(backup_src_dir,
@ -2106,13 +2177,29 @@ def backup_restore_do(restore_info,
template = host_collection.get_vm_by_name(template_name) template = host_collection.get_vm_by_name(template_name)
new_vm = None new_vm = None
vm_name = vm.name
if 'rename-to' in restore_info[vm.name]:
vm_name = restore_info[vm.name]['rename-to']
try: try:
new_vm = host_collection.add_new_vm(vm_class_name, name=vm.name, new_vm = host_collection.add_new_vm(vm_class_name, name=vm_name,
conf_file=vm.conf_file,
dir_path=vm.dir_path,
template=template, template=template,
installed_by_rpm=False) installed_by_rpm=False)
if os.path.exists(new_vm.dir_path):
move_to_path = tempfile.mkdtemp('', os.path.basename(
new_vm.dir_path), os.path.dirname(new_vm.dir_path))
try:
os.rename(new_vm.dir_path, move_to_path)
error_callback(
"*** Directory {} already exists! It has "
"been moved to {}".format(new_vm.dir_path,
move_to_path))
except OSError:
error_callback(
"*** Directory {} already exists and "
"cannot be moved!".format(new_vm.dir_path))
error_callback("Skipping...")
continue
if format_version == 1: if format_version == 1:
restore_vm_dir_v1(backup_location, restore_vm_dir_v1(backup_location,
@ -2156,7 +2243,11 @@ def backup_restore_do(restore_info,
# Set network dependencies - only non-default netvm setting # Set network dependencies - only non-default netvm setting
for vm in vms.values(): for vm in vms.values():
host_vm = host_collection.get_vm_by_name(vm.name) vm_name = vm.name
if 'rename-to' in restore_info[vm.name]:
vm_name = restore_info[vm.name]['rename-to']
host_vm = host_collection.get_vm_by_name(vm_name)
if host_vm is None: if host_vm is None:
# Failed/skipped VM # Failed/skipped VM
continue continue
@ -2205,7 +2296,7 @@ def backup_restore_do(restore_info,
home_dir + '/' + restore_home_backupdir + '/' + f) home_dir + '/' + restore_home_backupdir + '/' + f)
if format_version == 1: if format_version == 1:
subprocess.call( subprocess.call(
["cp", "-nrp", backup_dom0_home_dir + '/' + f, home_file]) ["cp", "-nrp", "--reflink=auto", backup_dom0_home_dir + '/' + f, home_file])
elif format_version >= 2: elif format_version >= 2:
shutil.move(backup_dom0_home_dir + '/' + f, home_file) shutil.move(backup_dom0_home_dir + '/' + f, home_file)
retcode = subprocess.call(['sudo', 'chown', '-R', local_user, home_dir]) retcode = subprocess.call(['sudo', 'chown', '-R', local_user, home_dir])

View File

@ -65,7 +65,7 @@ defaults = {
'libvirt_uri': 'xen:///', 'libvirt_uri': 'xen:///',
'memory': 400, 'memory': 400,
'kernelopts': "nopat", 'kernelopts': "nopat",
'kernelopts_pcidevs': "nopat iommu=soft swiotlb=4096", 'kernelopts_pcidevs': "nopat iommu=soft swiotlb=8192",
'dom0_update_check_interval': 6*3600, 'dom0_update_check_interval': 6*3600,

View File

@ -29,7 +29,7 @@ from lxml import etree
from lxml.etree import ElementTree, SubElement, Element from lxml.etree import ElementTree, SubElement, Element
from qubes.qubes import QubesException from qubes.qubes import QubesException
from qubes.qubes import vmm from qubes.qubes import vmm,defaults
from qubes.qubes import system_path,vm_files from qubes.qubes import system_path,vm_files
import sys import sys
import os import os
@ -297,6 +297,8 @@ def block_check_attached(qvmc, device):
if vm.qid == 0: if vm.qid == 0:
# Connecting devices to dom0 not supported # Connecting devices to dom0 not supported
continue continue
if not vm.is_running():
continue
try: try:
libvirt_domain = vm.libvirt_domain libvirt_domain = vm.libvirt_domain
if libvirt_domain: if libvirt_domain:
@ -313,9 +315,8 @@ def block_check_attached(qvmc, device):
disks = parsed_xml.xpath("//domain/devices/disk") disks = parsed_xml.xpath("//domain/devices/disk")
for disk in disks: for disk in disks:
backend_name = 'dom0' backend_name = 'dom0'
# FIXME: move <domain/> into <source/> if disk.find('backenddomain') is not None:
if disk.find('domain') is not None: backend_name = disk.find('backenddomain').get('name')
backend_name = disk.find('domain').get('name')
source = disk.find('source') source = disk.find('source')
if disk.get('type') == 'file': if disk.get('type') == 'file':
path = source.get('file') path = source.get('file')
@ -696,11 +697,16 @@ class QubesWatch(object):
self.block_callback = None self.block_callback = None
self.meminfo_callback = None self.meminfo_callback = None
self.domain_callback = None self.domain_callback = None
vmm.libvirt_conn.domainEventRegisterAny( libvirt.virEventRegisterDefaultImpl()
# open new libvirt connection because above
# virEventRegisterDefaultImpl is in practice effective only for new
# connections
self.libvirt_conn = libvirt.open(defaults['libvirt_uri'])
self.libvirt_conn.domainEventRegisterAny(
None, None,
libvirt.VIR_DOMAIN_EVENT_ID_LIFECYCLE, libvirt.VIR_DOMAIN_EVENT_ID_LIFECYCLE,
self._domain_list_changed, None) self._domain_list_changed, None)
vmm.libvirt_conn.domainEventRegisterAny( self.libvirt_conn.domainEventRegisterAny(
None, None,
libvirt.VIR_DOMAIN_EVENT_ID_DEVICE_REMOVED, libvirt.VIR_DOMAIN_EVENT_ID_DEVICE_REMOVED,
self._device_removed, None) self._device_removed, None)
@ -709,10 +715,10 @@ class QubesWatch(object):
try: try:
if vm.isActive(): if vm.isActive():
self._register_watches(vm) self._register_watches(vm)
except libvirt.libvirtError: except libvirt.libvirtError as e:
# this will happen if we loose a race with another tool, # this will happen if we loose a race with another tool,
# which can just remove the domain # which can just remove the domain
if vmm.libvirt_conn.virConnGetLastError()[0] == libvirt.VIR_ERR_NO_DOMAIN: if e.get_error_code() == libvirt.VIR_ERR_NO_DOMAIN:
pass pass
raise raise
# and for dom0 # and for dom0
@ -749,29 +755,41 @@ class QubesWatch(object):
name = libvirt_domain.name() name = libvirt_domain.name()
if name in self._qdb: if name in self._qdb:
return return
if not libvirt_domain.isActive():
return
# open separate connection to Qubes DB: # open separate connection to Qubes DB:
# 1. to not confuse pull() with responses to real commands sent from # 1. to not confuse pull() with responses to real commands sent from
# other threads (like read, write etc) with watch events # other threads (like read, write etc) with watch events
# 2. to not think whether QubesDB is thread-safe (it isn't) # 2. to not think whether QubesDB is thread-safe (it isn't)
while libvirt_domain.isActive() and name not in self._qdb: try:
try: self._qdb[name] = QubesDB(name)
self._qdb[name] = QubesDB(name) except Error as e:
except Error as e: if e.args[0] != 2:
if e.args[0] != 2: raise
raise libvirt.virEventAddTimeout(500, self._retry_register_watches,
time.sleep(0.5) libvirt_domain)
if name not in self._qdb:
# domain no longer active
return return
else: else:
name = "dom0" name = "dom0"
self._qdb[name] = QubesDB(name) self._qdb[name] = QubesDB(name)
self._qdb[name].watch('/qubes-block-devices') try:
self._qdb[name].watch('/qubes-block-devices')
except Error as e:
if e.args[0] == 102: # Connection reset by peer
# QubesDB daemon not running - most likely we've connected to
# stale daemon which just exited; retry later
libvirt.virEventAddTimeout(500, self._retry_register_watches,
libvirt_domain)
return
self._qdb_events[name] = libvirt.virEventAddHandle( self._qdb_events[name] = libvirt.virEventAddHandle(
self._qdb[name].watch_fd(), self._qdb[name].watch_fd(),
libvirt.VIR_EVENT_HANDLE_READABLE, libvirt.VIR_EVENT_HANDLE_READABLE,
self._qdb_handler, name) self._qdb_handler, name)
def _retry_register_watches(self, timer, libvirt_domain):
libvirt.virEventRemoveTimeout(timer)
self._register_watches(libvirt_domain)
def _unregister_watches(self, libvirt_domain): def _unregister_watches(self, libvirt_domain):
name = libvirt_domain.name() name = libvirt_domain.name()
if name in self._qdb_events: if name in self._qdb_events:
@ -782,7 +800,9 @@ class QubesWatch(object):
del(self._qdb[name]) del(self._qdb[name])
def _domain_list_changed(self, conn, domain, event, reason, param): def _domain_list_changed(self, conn, domain, event, reason, param):
if event == libvirt.VIR_DOMAIN_EVENT_STARTED: # use VIR_DOMAIN_EVENT_RESUMED instead of VIR_DOMAIN_EVENT_STARTED to
# make sure that qubesdb daemon is already running
if event == libvirt.VIR_DOMAIN_EVENT_RESUMED:
self._register_watches(domain) self._register_watches(domain)
elif event == libvirt.VIR_DOMAIN_EVENT_STOPPED: elif event == libvirt.VIR_DOMAIN_EVENT_STOPPED:
self._unregister_watches(domain) self._unregister_watches(domain)
@ -802,9 +822,24 @@ class QubesWatch(object):
##### updates check ##### ##### updates check #####
#
# XXX this whole section is a new global property
# TODO make event handlers
#
UPDATES_DOM0_DISABLE_FLAG='/var/lib/qubes/updates/disable-updates' UPDATES_DOM0_DISABLE_FLAG='/var/lib/qubes/updates/disable-updates'
UPDATES_DEFAULT_VM_DISABLE_FLAG=\
'/var/lib/qubes/updates/vm-default-disable-updates'
def updates_vms_toggle(qvm_collection, value): def updates_vms_toggle(qvm_collection, value):
# Flag for new VMs
if value:
if os.path.exists(UPDATES_DEFAULT_VM_DISABLE_FLAG):
os.unlink(UPDATES_DEFAULT_VM_DISABLE_FLAG)
else:
open(UPDATES_DEFAULT_VM_DISABLE_FLAG, "w").close()
# Change for existing VMs
for vm in qvm_collection.values(): for vm in qvm_collection.values():
if vm.qid == 0: if vm.qid == 0:
continue continue
@ -834,5 +869,16 @@ def updates_dom0_toggle(qvm_collection, value):
def updates_dom0_status(qvm_collection): def updates_dom0_status(qvm_collection):
return not os.path.exists(UPDATES_DOM0_DISABLE_FLAG) return not os.path.exists(UPDATES_DOM0_DISABLE_FLAG)
def updates_vms_status(qvm_collection):
# default value:
status = not os.path.exists(UPDATES_DEFAULT_VM_DISABLE_FLAG)
# check if all the VMs uses the default value
for vm in qvm_collection.values():
if vm.qid == 0:
continue
if vm.services.get('qubes-update-check', True) != status:
# "mixed"
return None
return status
# vim:sw=4:et: # vim:sw=4:et:

View File

@ -1,45 +0,0 @@
#!/usr/bin/python2
from __future__ import absolute_import
import _winreg
import os
import sys
from qubes.storage.wni import QubesWniVmStorage
DEFAULT_INSTALLDIR = 'c:\\program files\\Invisible Things Lab\\Qubes WNI'
DEFAULT_STOREDIR = 'c:\\qubes'
def apply(system_path, vm_files, defaults):
system_path['qubes_base_dir'] = DEFAULT_STOREDIR
installdir = DEFAULT_INSTALLDIR
try:
reg_key = _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE,
"Software\\Invisible Things Lab\\Qubes WNI")
installdir = _winreg.QueryValueEx(reg_key, "InstallDir")[0]
system_path['qubes_base_dir'] = \
_winreg.QueryValueEx(reg_key, "StoreDir")[0]
except WindowsError as e:
print >>sys.stderr, \
"WARNING: invalid installation: missing registry entries (%s)" \
% str(e)
system_path['config_template_pv'] = \
os.path.join(installdir, 'vm-template.xml')
system_path['config_template_hvm'] = \
os.path.join(installdir, 'vm-template-hvm.xml')
system_path['qubes_icon_dir'] = os.path.join(installdir, 'icons')
system_path['qubesdb_daemon_path'] = \
os.path.join(installdir, 'bin\\qubesdb-daemon.exe')
system_path['qrexec_daemon_path'] = \
os.path.join(installdir, 'bin\\qrexec-daemon.exe')
system_path['qrexec_client_path'] = \
os.path.join(installdir, 'bin\\qrexec-client.exe')
system_path['qrexec_policy_dir'] = \
os.path.join(installdir, 'qubes-rpc\\policy')
# Specific to WNI - normally VM have this file
system_path['qrexec_agent_path'] = \
os.path.join(installdir, 'bin\\qrexec-agent.exe')
defaults['libvirt_uri'] = 'wni:///'
defaults['storage_class'] = QubesWniVmStorage

View File

@ -2,7 +2,10 @@
from __future__ import absolute_import from __future__ import absolute_import
from qubes.storage.xen import QubesXenVmStorage from qubes.storage.xen import XenStorage, XenPool
def apply(system_path, vm_files, defaults): def apply(system_path, vm_files, defaults):
defaults['storage_class'] = QubesXenVmStorage defaults['storage_class'] = XenStorage
defaults['pool_drivers'] = {'xen': XenPool}
defaults['pool_config'] = {'dir_path': '/var/lib/qubes/'}

View File

@ -1,5 +1,6 @@
OS ?= Linux OS ?= Linux
SYSCONFDIR ?= /etc
PYTHON_QUBESPATH = $(PYTHON_SITEPATH)/qubes PYTHON_QUBESPATH = $(PYTHON_SITEPATH)/qubes
all: all:
@ -13,6 +14,8 @@ endif
mkdir -p $(DESTDIR)$(PYTHON_QUBESPATH)/storage mkdir -p $(DESTDIR)$(PYTHON_QUBESPATH)/storage
cp __init__.py $(DESTDIR)$(PYTHON_QUBESPATH)/storage cp __init__.py $(DESTDIR)$(PYTHON_QUBESPATH)/storage
cp __init__.py[co] $(DESTDIR)$(PYTHON_QUBESPATH)/storage cp __init__.py[co] $(DESTDIR)$(PYTHON_QUBESPATH)/storage
mkdir -p $(DESTDIR)$(SYSCONFDIR)/qubes
cp storage.conf $(DESTDIR)$(SYSCONFDIR)/qubes/
ifneq ($(BACKEND_VMM),) ifneq ($(BACKEND_VMM),)
if [ -r $(BACKEND_VMM).py ]; then \ if [ -r $(BACKEND_VMM).py ]; then \
cp $(BACKEND_VMM).py $(DESTDIR)$(PYTHON_QUBESPATH)/storage && \ cp $(BACKEND_VMM).py $(DESTDIR)$(PYTHON_QUBESPATH)/storage && \

View File

@ -1,138 +0,0 @@
#!/usr/bin/python2
#
# The Qubes OS Project, http://www.qubes-os.org
#
# Copyright (C) 2013 Marek Marczykowski <marmarek@invisiblethingslab.com>
#
# 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.
#
#
from __future__ import absolute_import
import sys
import os
import os.path
import win32api
import win32net
import win32netcon
import win32security
import win32profile
import pywintypes
import random
from qubes.storage import QubesVmStorage
from qubes.qubes import QubesException,system_path
class QubesWniVmStorage(QubesVmStorage):
"""
Class for VM storage of WNI VMs.
"""
def __init__(self, *args, **kwargs):
super(QubesWniVmStorage, self).__init__(*args, **kwargs)
# Use the user profile as "private.img"
self.home_root = win32profile.GetProfilesDirectory()
# FIXME: the assignment below may not always be correct,
# but GetUserProfileDirectory needs a user token...
self.private_img = os.path.join(self.home_root, self._get_username())
# Pass paths for WNI libvirt driver
os.putenv("WNI_DRIVER_QUBESDB_PATH", system_path['qubesdb_daemon_path'])
os.putenv("WNI_DRIVER_QREXEC_AGENT_PATH", system_path['qrexec_agent_path'])
def _get_username(self, vmname = None):
if vmname is None:
vmname = self.vm.name
return "qubes-vm-%s" % vmname
def _get_random_password(self, vmname = None):
if vmname is None:
vmname = self.vm.name
return '%x' % random.SystemRandom().getrandombits(256)
def get_config_params(self):
return {}
def create_on_disk_private_img(self, verbose, source_template = None):
# FIXME: this may not always be correct
home_dir = os.path.join(self.home_root, self._get_username())
# Create user data in information level 1 (PyUSER_INFO_1) format.
user_data = {}
user_data['name'] = self._get_username()
user_data['full_name'] = self._get_username()
# libvirt driver doesn't need to know the password anymore
user_data['password'] = self._get_random_password()
user_data['flags'] = (
win32netcon.UF_NORMAL_ACCOUNT |
win32netcon.UF_SCRIPT |
win32netcon.UF_DONT_EXPIRE_PASSWD |
win32netcon.UF_PASSWD_CANT_CHANGE
)
user_data['priv'] = win32netcon.USER_PRIV_USER
user_data['home_dir'] = home_dir
user_data['max_storage'] = win32netcon.USER_MAXSTORAGE_UNLIMITED
# TODO: catch possible exception
win32net.NetUserAdd(None, 1, user_data)
def create_on_disk_root_img(self, verbose, source_template = None):
pass
def remove_from_disk(self):
try:
sid = win32security.LookupAccountName(None, self._get_username())[0]
string_sid = win32security.ConvertSidToStringSid(sid)
win32profile.DeleteProfile(string_sid)
win32net.NetUserDel(None, self._get_username())
except pywintypes.error, details:
if details[0] == 2221:
# "The user name cannot be found."
raise IOError("User %s doesn't exist" % self._get_username())
else:
raise
super(QubesWniVmStorage, self).remove_from_disk()
def rename(self, old_name, new_name):
super(QubesWniVmStorage, self).rename(old_name, new_name)
user_data = {}
user_data['name'] = self._get_username(new_name)
win32net.NetUserSetInfo(None,
self._get_username(old_name), 0, user_data)
#TODO: rename user profile
def verify_files(self):
if not os.path.exists (self.vmdir):
raise QubesException (
"VM directory doesn't exist: {0}".\
format(self.vmdir))
try:
# TemplateVm in WNI is quite virtual, so do not require the user
if not self.vm.is_template():
win32net.NetUserGetInfo(None, self._get_username(), 0)
except pywintypes.error, details:
if details[0] == 2221:
# "The user name cannot be found."
raise QubesException("User %s doesn't exist" % self._get_username())
else:
raise
def reset_volatile_storage(self, verbose = False, source_template = None):
pass
def prepare_for_vm_startup(self, verbose = False):
if self.vm.is_template():
raise QubesException("Starting TemplateVM is not supported")

View File

@ -28,13 +28,12 @@ import sys
import shutil import shutil
import time import time
from qubes.qubes import QubesVmCollection from qubes.qubes import QubesVmCollection, QubesException
from qubes.qubes import QubesDispVmLabels from qubes.qubes import QubesDispVmLabels
from qubes.notify import tray_notify, tray_notify_error, tray_notify_init from qubes.notify import tray_notify, tray_notify_error, tray_notify_init
current_savefile = '/var/run/qubes/current-savefile' current_savefile = '/var/run/qubes/current-savefile'
current_dvm_conf = '/var/run/qubes/current-dvm.conf'
current_savefile_vmdir = '/var/lib/qubes/dvmdata/vmdir' current_savefile_vmdir = '/var/lib/qubes/dvmdata/vmdir'
@ -80,10 +79,13 @@ class QfileDaemonDvm:
label=label) label=label)
print >>sys.stderr, "time=%s, VM created" % (str(time.time())) print >>sys.stderr, "time=%s, VM created" % (str(time.time()))
# By default inherit firewall rules from calling VM # By default inherit firewall rules from calling VM
disp_firewall_conf = '/var/run/qubes/%s-firewall.xml' % dispvm.name
dispvm.firewall_conf = disp_firewall_conf
if os.path.exists(vm.firewall_conf): if os.path.exists(vm.firewall_conf):
disp_firewall_conf = '/var/run/qubes/%s-firewall.xml' % dispvm.name
shutil.copy(vm.firewall_conf, disp_firewall_conf) shutil.copy(vm.firewall_conf, disp_firewall_conf)
dispvm.firewall_conf = disp_firewall_conf elif vm.qid == 0 and os.path.exists(vm_disptempl.firewall_conf):
# for DispVM called from dom0, copy use rules from DispVM template
shutil.copy(vm_disptempl.firewall_conf, disp_firewall_conf)
if len(sys.argv) > 5 and len(sys.argv[5]) > 0: if len(sys.argv) > 5 and len(sys.argv[5]) > 0:
assert os.path.exists(sys.argv[5]), "Invalid firewall.conf location" assert os.path.exists(sys.argv[5]), "Invalid firewall.conf location"
dispvm.firewall_conf = sys.argv[5] dispvm.firewall_conf = sys.argv[5]
@ -99,7 +101,11 @@ class QfileDaemonDvm:
qvm_collection.unlock_db() qvm_collection.unlock_db()
return None return None
print >>sys.stderr, "time=%s, VM starting" % (str(time.time())) print >>sys.stderr, "time=%s, VM starting" % (str(time.time()))
dispvm.start() try:
dispvm.start()
except (MemoryError, QubesException) as e:
tray_notify_error(str(e))
raise
if vm.qid != 0: if vm.qid != 0:
# if need to enable/disable netvm, do it while DispVM is alive # if need to enable/disable netvm, do it while DispVM is alive
if (dispvm.netvm is None) != (vm.dispvm_netvm is None): if (dispvm.netvm is None) != (vm.dispvm_netvm is None):
@ -176,7 +182,11 @@ def main():
subprocess.call(['/usr/lib/qubes/qrexec-client', '-d', dispvm.name, subprocess.call(['/usr/lib/qubes/qrexec-client', '-d', dispvm.name,
user+':exec /usr/lib/qubes/qubes-rpc-multiplexer ' + user+':exec /usr/lib/qubes/qubes-rpc-multiplexer ' +
exec_index + " " + src_vmname]) exec_index + " " + src_vmname])
dispvm.force_shutdown() try:
dispvm.force_shutdown()
except QubesException:
# VM already destroyed
pass
qfile.remove_disposable_from_qdb(dispvm.name) qfile.remove_disposable_from_qdb(dispvm.name)
main() main()

View File

@ -70,5 +70,12 @@ else
fi fi
ln -snf $VMDIR /var/lib/qubes/dvmdata/vmdir ln -snf $VMDIR /var/lib/qubes/dvmdata/vmdir
cd $VMDIR cd $VMDIR
bsdtar -cSf saved-cows.tar volatile.img fstype=`df --output=fstype $VMDIR | tail -n 1`
if [ "$fstype" = "tmpfs" ]; then
# bsdtar doesn't work on tmpfs because FS_IOC_FIEMAP ioctl isn't supported
# there
tar -cSf saved-cows.tar volatile.img
else
bsdtar -cSf saved-cows.tar volatile.img
fi
echo "DVM savefile created successfully." echo "DVM savefile created successfully."

View File

@ -6,10 +6,8 @@ printf "\x00\x00\x00\x00" > /var/run/qubes/dispVM.seq
chown root:qubes /var/run/qubes/dispVM.seq chown root:qubes /var/run/qubes/dispVM.seq
chmod 660 /var/run/qubes/dispVM.seq chmod 660 /var/run/qubes/dispVM.seq
DEFAULT=/var/lib/qubes/dvmdata/default-savefile DEFAULT=/var/lib/qubes/dvmdata/default-savefile
DEFAULT_CONFIG=/var/lib/qubes/dvmdata/default-dvm.conf
# setup DispVM files only when they exists # setup DispVM files only when they exists
if [ -r $DEFAULT ]; then if [ -r $DEFAULT ]; then
ln -s $DEFAULT_CONFIG /var/run/qubes/current-dvm.conf
if [ -f /var/lib/qubes/dvmdata/dont-use-shm ] ; then if [ -f /var/lib/qubes/dvmdata/dont-use-shm ] ; then
ln -s $DEFAULT /var/run/qubes/current-savefile ln -s $DEFAULT /var/run/qubes/current-savefile
else else

View File

@ -25,6 +25,12 @@ Options
Add a PCI device to specified VM Add a PCI device to specified VM
.. option:: --add-class, -C
Add all devices of given class:
net - network interfaces
usb - USB controllers
.. option:: --delete, -d .. option:: --delete, -d
Remove a PCI device from specified VM Remove a PCI device from specified VM

View File

@ -55,12 +55,11 @@ include_in_backups
Accepted values: ``True``, ``False`` Accepted values: ``True``, ``False``
Control whenever this VM will be included in backups by default (for now Control whenever this VM will be included in backups by default (for now
works only in qubes-manager). You can always manually select or deselect works only in qubes-manager). You can always manually select or
any VM for backup. deselect any VM for backup.
pcidevs pcidevs
PCI devices assigned to the VM. Should be edited using PCI devices assigned to the VM. Should be edited using qvm-pci tool.
:manpage:`qvm-pci(1)` tool.
pci_strictreset pci_strictreset
Accepted values: ``True``, ``False`` Accepted values: ``True``, ``False``
@ -86,23 +85,19 @@ netvm
default NetVM (managed by qubes-prefs). Setting to ``none`` will disable default NetVM (managed by qubes-prefs). Setting to ``none`` will disable
networking in this VM. networking in this VM.
.. note::
When setting to ``none``, firewall will be set to block all traffic -
it will be used by DispVM started from this VM. Setting back to some
NetVM will *NOT* restore previous firewall settings.
dispvm_netvm dispvm_netvm
Accepted values: netvm name, ``default``, ``none`` Accepted values: netvm name, ``default``, ``none``
Which NetVM should be used for Disposable VMs started by this one. ``default`` is to use the same NetVM as the VM itself. Which NetVM should be used for Disposable VMs started by this one.
``default`` is to use the same NetVM as the VM itself.
maxmem maxmem
Accepted values: memory size in MB Accepted values: memory size in MB
Maximum memory size available for this VM. Dynamic memory management (aka Maximum memory size available for this VM. Dynamic memory management (aka
qmemman) will not be able to balloon over this limit. For VMs with qmemman qmemman) will not be able to balloon over this limit. For VMs with
disabled, this will be overridden by *memory* property (at VM startup). qmemman disabled, this will be overridden by *memory* property (at VM
startup).
memory memory
Accepted values: memory size in MB Accepted values: memory size in MB
@ -115,12 +110,11 @@ kernel
Accepted values: kernel version, ``default``, ``none`` Accepted values: kernel version, ``default``, ``none``
Kernel version to use (only for PV VMs). Available kernel versions will be Kernel version to use (only for PV VMs). Available kernel versions will be
listed when no value given (there are in listed when no value given (there are in /var/lib/qubes/vm-kernels).
:file:`/var/lib/qubes/vm-kernels`). Setting to ``default`` will follow Setting to ``default`` will follow system-global default kernel (managed
system-global default kernel (managed via qubes-prefs). Setting to ``none`` via qubes-prefs). Setting to ``none`` will use "kernels" subdir in
will use "kernels" subdir in VM directory - this allows having VM-specific VM directory - this allows having VM-specific kernel; also this the only
kernel; also this the only case when :file:`/lib/modules` is writable from case when /lib/modules is writable from within VM.
within VM.
template template
Accepted values: TemplateVM name Accepted values: TemplateVM name
@ -139,8 +133,10 @@ kernelopts
VM kernel parameters (available only for PV VMs). This can be used to VM kernel parameters (available only for PV VMs). This can be used to
workaround some hardware specific problems (eg for NetVM). Setting to workaround some hardware specific problems (eg for NetVM). Setting to
``default`` will use some reasonable defaults (currently different for VMs ``default`` will use some reasonable defaults (currently different for VMs
with PCI devices and without). Some helpful options (for debugging with PCI devices and without). For VM without PCI devices
purposes): ``earlyprintk=xen``, ``init=/bin/bash`` ``default`` option means inherit this value from the VM template (if any).
Some helpful options (for debugging purposes): ``earlyprintk=xen``,
``init=/bin/bash``
name name
Accepted values: alphanumerical name Accepted values: alphanumerical name
@ -148,12 +144,12 @@ name
Name of the VM. Can be only changed when VM isn't running. Name of the VM. Can be only changed when VM isn't running.
drive drive
Accepted values: [hd:\|cdrom:][backend-vm:]\ *path* Accepted values: [hd:\|cdrom:][backend-vm:]path
Additional drive for the VM (available only for HVMs). This can be used to Additional drive for the VM (available only for HVMs). This can be used to
attach installation image. ``path`` can be file or physical device (eg. attach installation image. ``path`` can be file or physical device (eg.
:file:`/dev/sr0`). The same syntax can be used in :option:`qvm-start :file:`/dev/sr0`). The same syntax can be used in
--drive` - to attach drive only temporarily. :option:`qvm-start --drive` - to attach drive only temporarily.
mac mac
Accepted values: MAC address, ``auto`` Accepted values: MAC address, ``auto``
@ -161,7 +157,6 @@ mac
Can be used to force specific of virtual ethernet card in the VM. Setting Can be used to force specific of virtual ethernet card in the VM. Setting
to ``auto`` will use automatic-generated MAC - based on VM id. Especially to ``auto`` will use automatic-generated MAC - based on VM id. Especially
useful when some licencing depending on static MAC address. useful when some licencing depending on static MAC address.
For template-based HVM ``auto`` mode means to clone template MAC. For template-based HVM ``auto`` mode means to clone template MAC.
default_user default_user
@ -181,8 +176,8 @@ debug
:file:`root.img` (actually :file:`volatile.img`) before each VM startup, so :file:`root.img` (actually :file:`volatile.img`) before each VM startup, so
changes made to root filesystem stays intact. To force reset changes made to root filesystem stays intact. To force reset
:file:`root.img` when debug mode enabled, either change something in the :file:`root.img` when debug mode enabled, either change something in the
template (simple start+stop will do, even touch its root.img is enough), or template (simple start+stop will do, even touch its :file:`root.img` is
remove VM's :file:`volatile.img` (check the path with enough), or remove VM's :file:`volatile.img` (check the path with
:manpage:`qvm-prefs(1)`). :manpage:`qvm-prefs(1)`).
qrexec_installed qrexec_installed
@ -199,7 +194,7 @@ guiagent_installed
Accepted values: ``True``, ``False`` Accepted values: ``True``, ``False``
This HVM have gui agent installed. This option disables full screen GUI This HVM have gui agent installed. This option disables full screen GUI
virtualization and enables per-window seamless GUI mode. This option will virtualization and enables per-window seemless GUI mode. This option will
be automatically turned on during Qubes Windows Tools installation, but if be automatically turned on during Qubes Windows Tools installation, but if
you install qubes gui agent in some other OS, you need to turn this option you install qubes gui agent in some other OS, you need to turn this option
on manually. You can turn this option off to troubleshoot some early HVM OS on manually. You can turn this option off to troubleshoot some early HVM OS
@ -208,7 +203,7 @@ guiagent_installed
startup). startup).
.. note:: .. note::
when Windows GUI agent is installed in the VM, SVGA device (used to when Windows GUI agent is installed in the VM, SVGA device (used to
full screen video) is disabled, so even if you disable this option, you full screen video) is disabled, so even if you disable this option, you
will not get functional full desktop access (on normal VM startup). Use will not get functional full desktop access (on normal VM startup). Use

View File

@ -92,7 +92,7 @@ cups
Enable CUPS service. The user can disable cups in VM which do not need Enable CUPS service. The user can disable cups in VM which do not need
printing to speed up booting. printing to speed up booting.
cron crond
Default: disabled Default: disabled
Enable CRON service. Enable CRON service.

12
etc/storage.conf Normal file
View File

@ -0,0 +1,12 @@
[default] ; poolname
driver=xen ; the default xen storage
; class = qubes.storage.xen.XenStorage ; class always overwrites the driver
;
; To use our own storage adapter, you need just to specify the module path and
; class name
; [pool-b]
; class = foo.bar.MyStorage
;
; [test-dummy]
; driver=dummy

View File

@ -6,6 +6,10 @@ fi
set -e set -e
if ! echo $PATH | grep -q sbin; then
PATH=$PATH:/sbin:/usr/sbin
fi
FILENAME=$1 FILENAME=$1
ROOT_SIZE=$2 ROOT_SIZE=$2
SWAP_SIZE=$[ 1024 ] SWAP_SIZE=$[ 1024 ]
@ -22,13 +26,3 @@ fi
TOTAL_SIZE=$[ $ROOT_SIZE + $SWAP_SIZE + 512 ] TOTAL_SIZE=$[ $ROOT_SIZE + $SWAP_SIZE + 512 ]
truncate -s ${TOTAL_SIZE}M "$FILENAME" truncate -s ${TOTAL_SIZE}M "$FILENAME"
sfdisk --no-reread -u M "$FILENAME" > /dev/null 2> /dev/null <<EOF
0,${SWAP_SIZE},S
,${ROOT_SIZE},L
EOF
loopdev=`losetup -f --show --partscan "$FILENAME"`
udevadm settle
mkswap -f ${loopdev}p1 > /dev/null
losetup -d ${loopdev} || :
chown --reference `dirname "$FILENAME"` "$FILENAME"

View File

@ -8,7 +8,7 @@ xenstore-write domid 0
DOM0_MAXMEM=`/usr/sbin/xl info | grep total_memory | awk '{ print $3 }'` DOM0_MAXMEM=`/usr/sbin/xl info | grep total_memory | awk '{ print $3 }'`
xenstore-write /local/domain/0/memory/static-max $[ $DOM0_MAXMEM * 1024 ] xenstore-write /local/domain/0/memory/static-max $[ $DOM0_MAXMEM * 1024 ]
xl sched-credit -d 0 -w 512 xl sched-credit -d 0 -w 2000
cp /var/lib/qubes/qubes.xml /var/lib/qubes/backup/qubes-$(date +%F-%T).xml cp /var/lib/qubes/qubes.xml /var/lib/qubes/backup/qubes-$(date +%F-%T).xml
/usr/lib/qubes/cleanup-dispvms /usr/lib/qubes/cleanup-dispvms

View File

@ -1,6 +1,6 @@
[Unit] [Unit]
Description=Qubes Dom0 startup setup Description=Qubes Dom0 startup setup
After=qubes-db-dom0.service libvirtd.service After=qubes-db-dom0.service libvirtd.service xenconsoled.service
# Cover legacy init.d script # Cover legacy init.d script
[Service] [Service]
@ -9,6 +9,9 @@ StandardOutput=syslog
RemainAfterExit=yes RemainAfterExit=yes
ExecStart=/usr/lib/qubes/startup-misc.sh ExecStart=/usr/lib/qubes/startup-misc.sh
ExecStop=/usr/bin/qvm-shutdown -q --all --wait ExecStop=/usr/bin/qvm-shutdown -q --all --wait
# QubesDB daemons stop after 60s timeout in worst case; speed it up, since no
# VMs are running now
ExecStop=-/usr/bin/killall qubesdb-daemon
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target

View File

@ -1,7 +1,7 @@
[Unit] [Unit]
Description=Qubes NetVM startup Description=Qubes NetVM startup
After=qubes-core.service qubes-qmemman.service libvirtd.service After=qubes-core.service qubes-qmemman.service libvirtd.service
Before=plymouth-quit.service Before=systemd-user-sessions.service
[Service] [Service]
Type=oneshot Type=oneshot

View File

@ -1,12 +1,12 @@
[Unit] [Unit]
Description=Start Qubes VM %i Description=Start Qubes VM %i
After=qubes-netvm.service After=qubes-netvm.service
Before=plymouth-quit.service Before=systemd-user-sessions.service
[Service] [Service]
Type=oneshot Type=oneshot
Environment=DISPLAY=:0 Environment=DISPLAY=:0
ExecStart=/usr/bin/qvm-start --no-guid %i ExecStart=/usr/bin/qvm-start --no-guid --skip-if-running %i
Group=qubes Group=qubes
RemainAfterExit=yes RemainAfterExit=yes

View File

@ -5,7 +5,6 @@ import re
import sys import sys
import subprocess import subprocess
from qubes.qubes import QubesVmCollection,QubesException,QubesHVm from qubes.qubes import QubesVmCollection,QubesException,QubesHVm
from qubes.qubes import xs
def main(): def main():
@ -15,6 +14,8 @@ def main():
print >> sys.stderr, 'This script must be called as qrexec service!' print >> sys.stderr, 'This script must be called as qrexec service!'
exit(1) exit(1)
prev_qrexec_installed = False
source_vm = None
qvm_collection = QubesVmCollection() qvm_collection = QubesVmCollection()
qvm_collection.lock_db_for_writing() qvm_collection.lock_db_for_writing()
try: try:
@ -66,6 +67,7 @@ def main():
else: else:
user = None user = None
prev_qrexec_installed = source_vm.qrexec_installed
# Let the tools to be able to enable *or disable* each particular component # Let the tools to be able to enable *or disable* each particular component
source_vm.qrexec_installed = qrexec > 0 source_vm.qrexec_installed = qrexec > 0
source_vm.guiagent_installed = gui > 0 source_vm.guiagent_installed = gui > 0
@ -75,15 +77,18 @@ def main():
qvm_collection.save() qvm_collection.save()
retcode = subprocess.call(['qvm-sync-appmenus', '--force-rpc'])
if retcode == 0 and hasattr(source_vm, 'appmenus_recreate'):
# TODO: call the same for child VMs? This isn't done for Linux VMs,
# so probably should be ignored for Windows also
source_vm.appmenus_recreate()
except Exception as e: except Exception as e:
print >> sys.stderr, e.message print >> sys.stderr, e.message
exit(1) exit(1)
finally: finally:
qvm_collection.unlock_db() qvm_collection.unlock_db()
if not prev_qrexec_installed and source_vm.qrexec_installed:
retcode = subprocess.call(['qvm-sync-appmenus', '--force-rpc'])
if retcode == 0 and hasattr(source_vm, 'appmenus_recreate'):
# TODO: call the same for child VMs? This isn't done for Linux VMs,
# so probably should be ignored for Windows also
source_vm.appmenus_recreate()
main() main()

View File

@ -44,7 +44,7 @@ def main():
source_vm = qvm_collection.get_vm_by_name(source) source_vm = qvm_collection.get_vm_by_name(source)
if source_vm is None: if source_vm is None:
print >> sys.stderr, 'Domain ' + source + ' does not exists (?!)' print >> sys.stderr, 'Domain ' + source + ' does not exist (?!)'
exit(1) exit(1)
os.umask(0002) os.umask(0002)
@ -61,7 +61,10 @@ def main():
update_f = open(source_vm.dir_path + '/' + vm_files["updates_stat_file"], "w") update_f = open(source_vm.dir_path + '/' + vm_files["updates_stat_file"], "w")
update_f.write(update_count) update_f.write(update_count)
update_f.close() update_f.close()
os.chown(source_vm.dir_path + '/' + vm_files["updates_stat_file"], -1, qubes_gid) try:
os.chown(source_vm.dir_path + '/' + vm_files["updates_stat_file"], -1, qubes_gid)
except OSError:
pass
elif source_vm.template is not None: elif source_vm.template is not None:
# Hint about updates availability in template # Hint about updates availability in template
# If template is running - it will notify about updates itself # If template is running - it will notify about updates itself
@ -79,7 +82,10 @@ def main():
update_f = open(stat_file, "w") update_f = open(stat_file, "w")
update_f.write(update_count) update_f.write(update_count)
update_f.close() update_f.close()
os.chown(stat_file, -1, qubes_gid) try:
os.chown(stat_file, -1, qubes_gid)
except OSError:
pass
else: else:
print >> sys.stderr, 'Ignoring notification of no updates' print >> sys.stderr, 'Ignoring notification of no updates'

View File

@ -1142,6 +1142,11 @@ class Qubes(PropertyHolder):
default_kernel = property('default_kernel', load_stage=3, default_kernel = property('default_kernel', load_stage=3,
doc='Which kernel to use when not overriden in VM') doc='Which kernel to use when not overriden in VM')
# TODO #1637 #892
check_updates_vm = property('check_updates_vm',
type=bool, setter=property.bool,
doc='check for updates inside qubes')
def __init__(self, store=None, load=True, **kwargs): def __init__(self, store=None, load=True, **kwargs):
#: logger instance for logging global messages #: logger instance for logging global messages

View File

@ -22,6 +22,12 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
# #
#
# THIS FILE SHOULD BE CONFIGURED PER PRODUCT
# or better, once first custom product arrives,
# make a real /etc/qubes/master.conf or whatever
#
'''Constants which can be configured in one place''' '''Constants which can be configured in one place'''
qubes_base_dir = "/var/lib/qubes" qubes_base_dir = "/var/lib/qubes"
@ -69,14 +75,16 @@ defaults = {
'libvirt_uri': 'xen:///', 'libvirt_uri': 'xen:///',
'memory': 400, 'memory': 400,
'kernelopts': "nopat", 'kernelopts': "nopat",
'kernelopts_pcidevs': "nopat iommu=soft swiotlb=4096", 'kernelopts_pcidevs': "nopat iommu=soft swiotlb=8192",
'dom0_update_check_interval': 6*3600, 'dom0_update_check_interval': 6*3600,
'private_img_size': 2*1024*1024*1024, 'private_img_size': 2*1024*1024*1024,
'root_img_size': 10*1024*1024*1024, 'root_img_size': 10*1024*1024*1024,
'storage_class': 'qubes.storage.xen.XenVMStorage', 'storage_class': 'qubes.storage.xen.XenStorage',
'pool_drivers': {'xen': 'qubes.storage.xen.XenPool'}
'pool_config': {'dir_path': '/var/lib/qubes'}
# how long (in sec) to wait for VMs to shutdown, # how long (in sec) to wait for VMs to shutdown,
# before killing them (when used qvm-run with --wait option), # before killing them (when used qvm-run with --wait option),

View File

@ -42,7 +42,9 @@ slow_memset_react_msg="VM didn't give back all requested memory"
class DomainState: class DomainState:
def __init__(self, id): def __init__(self, id):
self.meminfo = None #dictionary of memory info read from client self.meminfo = None #dictionary of memory info read from client
self.memory_actual = None #the current memory size self.memory_current = 0 # the current memory size
self.memory_actual = None # the current memory allocation (what VM
# is using or can use at any time)
self.memory_maximum = None #the maximum memory size self.memory_maximum = None #the maximum memory size
self.mem_used = None #used memory, computed based on meminfo self.mem_used = None #used memory, computed based on meminfo
self.id = id #domain id self.id = id #domain id
@ -50,6 +52,9 @@ class DomainState:
self.no_progress = False #no react to memset self.no_progress = False #no react to memset
self.slow_memset_react = False #slow react to memset (after few tries still above target) self.slow_memset_react = False #slow react to memset (after few tries still above target)
def __repr__(self):
return self.__dict__.__repr__()
class SystemState(object): class SystemState(object):
def __init__(self): def __init__(self):
self.log = logging.getLogger('qmemman.systemstate') self.log = logging.getLogger('qmemman.systemstate')
@ -61,30 +66,62 @@ class SystemState(object):
self.BALOON_DELAY = 0.1 self.BALOON_DELAY = 0.1
self.XEN_FREE_MEM_LEFT = 50*1024*1024 self.XEN_FREE_MEM_LEFT = 50*1024*1024
self.XEN_FREE_MEM_MIN = 25*1024*1024 self.XEN_FREE_MEM_MIN = 25*1024*1024
self.ALL_PHYS_MEM = self.xc.physinfo()['total_memory']*1024 # Overhead of per-page Xen structures, taken from OpenStack nova/virt/xenapi/driver.py
# see https://wiki.openstack.org/wiki/XenServer/Overhead
# we divide total and free physical memory by this to get "assignable" memory
self.MEM_OVERHEAD_FACTOR = 1.0 / 1.00781
self.ALL_PHYS_MEM = int(self.xc.physinfo()['total_memory']*1024 * self.MEM_OVERHEAD_FACTOR)
def add_domain(self, id): def add_domain(self, id):
self.log.debug('add_domain(id={!r})'.format(id)) self.log.debug('add_domain(id={!r})'.format(id))
self.domdict[id] = DomainState(id) self.domdict[id] = DomainState(id)
# TODO: move to DomainState.__init__
target_str = self.xs.read('', '/local/domain/' + id + '/memory/target')
if target_str:
self.domdict[id].last_target = int(target_str) * 1024
def del_domain(self, id): def del_domain(self, id):
self.log.debug('del_domain(id={!r})'.format(id)) self.log.debug('del_domain(id={!r})'.format(id))
self.domdict.pop(id) self.domdict.pop(id)
def get_free_xen_memory(self): def get_free_xen_memory(self):
return self.xc.physinfo()['free_memory']*1024 xen_free = int(self.xc.physinfo()['free_memory']*1024 *
# hosts = self.xend_session.session.xenapi.host.get_all() self.MEM_OVERHEAD_FACTOR)
# host_record = self.xend_session.session.xenapi.host.get_record(hosts[0]) # now check for domains which have assigned more memory than really
# host_metrics_record = self.xend_session.session.xenapi.host_metrics.get_record(host_record["metrics"]) # used - do not count it as "free", because domain is free to use it
# ret = host_metrics_record["memory_free"] # at any time
# return long(ret) # assumption: self.refresh_memactual was called before
# (so domdict[id].memory_actual is up to date)
assigned_but_unused = reduce(
lambda acc, dom: acc + max(0, dom.last_target-dom.memory_current),
self.domdict.values(),
0
)
# If, at any time, Xen have less memory than XEN_FREE_MEM_MIN,
# it is a failure of qmemman. Collect as much data as possible to
# debug it
if xen_free < self.XEN_FREE_MEM_MIN:
self.log.error("Xen free = {!r} below acceptable value! "
"assigned_but_unused={!r}, domdict={!r}".format(
xen_free, assigned_but_unused, self.domdict))
elif xen_free < assigned_but_unused+self.XEN_FREE_MEM_MIN:
self.log.error("Xen free = {!r} too small for satisfy assignments! "
"assigned_but_unused={!r}, domdict={!r}".format(
xen_free, assigned_but_unused, self.domdict))
return xen_free - assigned_but_unused
#refresh information on memory assigned to all domains #refresh information on memory assigned to all domains
def refresh_memactual(self): def refresh_memactual(self):
for domain in self.xc.domain_getinfo(): for domain in self.xc.domain_getinfo():
id = str(domain['domid']) id = str(domain['domid'])
if self.domdict.has_key(id): if self.domdict.has_key(id):
self.domdict[id].memory_actual = domain['mem_kb']*1024 # real memory usage
self.domdict[id].memory_current = domain['mem_kb']*1024
# what VM is using or can use
self.domdict[id].memory_actual = max(
self.domdict[id].memory_current,
self.domdict[id].last_target
)
self.domdict[id].memory_maximum = self.xs.read('', '/local/domain/%s/memory/static-max' % str(id)) self.domdict[id].memory_maximum = self.xs.read('', '/local/domain/%s/memory/static-max' % str(id))
if self.domdict[id].memory_maximum: if self.domdict[id].memory_maximum:
self.domdict[id].memory_maximum = int(self.domdict[id].memory_maximum)*1024 self.domdict[id].memory_maximum = int(self.domdict[id].memory_maximum)*1024
@ -151,21 +188,38 @@ class SystemState(object):
#perform memory ballooning, across all domains, to add "memsize" to Xen free memory #perform memory ballooning, across all domains, to add "memsize" to Xen free memory
def do_balloon(self, memsize): def do_balloon(self, memsize):
self.log.info('do_balloon(memsize={!r})'.format(memsize)) self.log.info('do_balloon(memsize={!r})'.format(memsize))
MAX_TRIES = 20 CHECK_PERIOD_S = 3
CHECK_MB_S = 100
niter = 0 niter = 0
prev_memory_actual = None prev_memory_actual = None
for i in self.domdict.keys(): for i in self.domdict.keys():
self.domdict[i].no_progress = False self.domdict[i].no_progress = False
#: number of loop iterations for CHECK_PERIOD_S seconds
check_period = max(1, int((CHECK_PERIOD_S + 0.0) / self.BALOON_DELAY))
#: number of free memory bytes expected to get during CHECK_PERIOD_S
#: seconds
check_delta = CHECK_PERIOD_S * CHECK_MB_S * 1024 * 1024
#: helper array for holding free memory size, CHECK_PERIOD_S seconds
#: ago, at every loop iteration
xenfree_ring = [0] * check_period
while True: while True:
self.log.debug('niter={:2d}/{:2d}'.format(niter, MAX_TRIES)) self.log.debug('niter={:2d}'.format(niter))
self.refresh_memactual() self.refresh_memactual()
xenfree = self.get_free_xen_memory() xenfree = self.get_free_xen_memory()
self.log.info('xenfree={!r}'.format(xenfree)) self.log.info('xenfree={!r}'.format(xenfree))
if xenfree >= memsize + self.XEN_FREE_MEM_MIN: if xenfree >= memsize + self.XEN_FREE_MEM_MIN:
self.inhibit_balloon_up() self.inhibit_balloon_up()
return True return True
# fail the request if over past CHECK_PERIOD_S seconds,
# we got less than CHECK_MB_S MB/s on average
ring_slot = niter % check_period
if niter >= check_period and xenfree < xenfree_ring[ring_slot] + check_delta:
return False
xenfree_ring[ring_slot] = xenfree
if prev_memory_actual is not None: if prev_memory_actual is not None:
for i in prev_memory_actual.keys(): for i in prev_memory_actual.keys():
if prev_memory_actual[i] == self.domdict[i].memory_actual: if prev_memory_actual[i] == self.domdict[i].memory_actual:
@ -174,7 +228,7 @@ class SystemState(object):
self.log.info('domain {} stuck at {}'.format(i, self.domdict[i].memory_actual)) self.log.info('domain {} stuck at {}'.format(i, self.domdict[i].memory_actual))
memset_reqs = qubes.qmemman.algo.balloon(memsize + self.XEN_FREE_MEM_LEFT - xenfree, self.domdict) memset_reqs = qubes.qmemman.algo.balloon(memsize + self.XEN_FREE_MEM_LEFT - xenfree, self.domdict)
self.log.info('memset_reqs={!r}'.format(memset_reqs)) self.log.info('memset_reqs={!r}'.format(memset_reqs))
if niter > MAX_TRIES or len(memset_reqs) == 0: if len(memset_reqs) == 0:
return False return False
prev_memory_actual = {} prev_memory_actual = {}
for i in memset_reqs: for i in memset_reqs:
@ -266,11 +320,11 @@ class SystemState(object):
self.log.debug('do_balance dom={!r} sleeping ntries={}'.format( self.log.debug('do_balance dom={!r} sleeping ntries={}'.format(
dom, ntries)) dom, ntries))
time.sleep(self.BALOON_DELAY) time.sleep(self.BALOON_DELAY)
self.refresh_memactual()
ntries -= 1 ntries -= 1
if ntries <= 0: if ntries <= 0:
# Waiting haven't helped; Find which domain get stuck and # Waiting haven't helped; Find which domain get stuck and
# abort balance (after distributing what we have) # abort balance (after distributing what we have)
self.refresh_memactual()
for rq2 in memset_reqs: for rq2 in memset_reqs:
dom2, mem2 = rq2 dom2, mem2 = rq2
if dom2 == dom: if dom2 == dom:

5
qubes/storage/README.md Normal file
View File

@ -0,0 +1,5 @@
# WNI File storage
Before v3.1 there existed a draft wni storage. You can find it in the git
history
(it was in /core/storage directory, now gone)

View File

@ -39,14 +39,19 @@ import qubes.exc
import qubes.utils import qubes.utils
BLKSIZE = 512 BLKSIZE = 512
CONFIG_FILE = '/etc/qubes/storage.conf'
class VMStorage(object): class Storage(object):
'''Class for handling VM virtual disks. '''Class for handling VM virtual disks.
This is base class for all other implementations, mostly with Xen on Linux This is base class for all other implementations, mostly with Xen on Linux
in mind. in mind.
''' # pylint: disable=abstract-class-little-used ''' # pylint: disable=abstract-class-little-used
root_img = None
private_img = None
volatile_img = None
def __init__(self, vm, private_img_size=None, root_img_size=None): def __init__(self, vm, private_img_size=None, root_img_size=None):
#: Domain for which we manage storage #: Domain for which we manage storage
@ -66,29 +71,54 @@ class VMStorage(object):
self.drive = None self.drive = None
@property def get_config_params(self):
def private_img(self): args = {}
'''Path to the private image''' args['rootdev'] = self.root_dev_config()
return self.abspath(qubes.config.vm_files['private_img']) args['privatedev'] = self.private_dev_config()
args['volatiledev'] = self.volatile_dev_config()
args['otherdevs'] = self.other_dev_config()
args['kerneldir'] = self.kernels_dir
return args
@property def root_dev_config(self):
def root_img(self): raise NotImplementedError()
'''Path to the root image'''
return self.vm.template.root_img if hasattr(self.vm, 'template') \
else self.abspath(qubes.config.vm_files['root_img'])
def private_dev_config(self):
raise NotImplementedError()
@property def volatile_dev_config(self):
def rootcow_img(self): raise NotImplementedError()
'''Path to the root COW image'''
return self.abspath(qubes.config.vm_files['rootcow_img'])
def other_dev_config(self)
if self.modules_img is not None:
return self.format_disk_dev(self.modules_img, None,
self.modules_dev, rw=self.modules_img_rw)
elif self.drive is not None:
(drive_type, drive_domain, drive_path) = self.drive.split(":")
if drive_type == 'hd':
drive_type = 'disk'
@property rw = (drive_type == 'disk')
def volatile_img(self):
'''Path to the volatile image''' if drive_domain.lower() == "dom0":
return self.abspath(qubes.config.vm_files['volatile_img']) drive_domain = None
return self.format_disk_dev(drive_path,
None,
self.modules_dev,
rw=rw,
type=drive_type,
domain=drive_domain)
else:
return ''
def format_disk_dev(self, path, script, vdev, rw=True, type='disk',
domain=None):
raise NotImplementedError()
@property @property
@ -111,7 +141,13 @@ class VMStorage(object):
Depending on domain, this may be global or inside domain's dir. Depending on domain, this may be global or inside domain's dir.
''' '''
return os.path.join(self.kernels_dir, 'modules.img')
modules_path = os.path.join(self.kernels_dir, 'modules.img')
if os.path.exists(modules_path):
return modules_path
else:
return None
@property @property
@ -142,7 +178,7 @@ class VMStorage(object):
# We prefer to use Linux's cp, because it nicely handles sparse files # We prefer to use Linux's cp, because it nicely handles sparse files
try: try:
subprocess.check_call(['cp', source, destination]) subprocess.check_call(['cp', '--reflink=auto', source, destination])
except subprocess.CalledProcessError: except subprocess.CalledProcessError:
raise IOError('Error while copying {!r} to {!r}'.format( raise IOError('Error while copying {!r} to {!r}'.format(
source, destination)) source, destination))
@ -208,7 +244,7 @@ class VMStorage(object):
.. note:: .. note::
The arguments are in different order than in :program:`cp` utility. The arguments are in different order than in :program:`cp` utility.
.. versionchange:: 3.0 .. versionchange:: 4.0
This is now dummy method that just passes everything to This is now dummy method that just passes everything to
:py:func:`os.rename`. :py:func:`os.rename`.
@ -256,7 +292,7 @@ class VMStorage(object):
# For StandaloneVM create it only if not already exists # For StandaloneVM create it only if not already exists
# (eg after backup-restore) # (eg after backup-restore)
if hasattr(self.vm, 'volatile_img') \ if hasattr(self, 'volatile_img') \
and not os.path.exists(self.vm.volatile_img): and not os.path.exists(self.vm.volatile_img):
self.vm.log.info( self.vm.log.info(
'Creating volatile image: {0}'.format(self.volatile_img)) 'Creating volatile image: {0}'.format(self.volatile_img))
@ -317,13 +353,198 @@ def get_disk_usage(path):
return ret return ret
def get_storage(vm): #def get_storage(vm):
'''Factory yielding storage class instances for domains. # '''Factory yielding storage class instances for domains.
#
# :raises ImportError: when storage class specified in config cannot be found
# :raises KeyError: when storage class specified in config cannot be found
# '''
# pkg, cls = qubes.config.defaults['storage_class'].strip().rsplit('.', 1)
#
# # this may raise ImportError or KeyError, that's okay
# return importlib.import_module(pkg).__dict__[cls](vm)
:raises ImportError: when storage class specified in config cannot be found
:raises KeyError: when storage class specified in config cannot be found
'''
pkg, cls = qubes.config.defaults['storage_class'].strip().rsplit('.', 1)
# this may raise ImportError or KeyError, that's okay def dump(o):
return importlib.import_module(pkg).__dict__[cls](vm) """ Returns a string represention of the given object
Args:
o (object): anything that response to `__module__` and `__class__`
Given the class :class:`qubes.storage.QubesVmStorage` it returns
'qubes.storage.QubesVmStorage' as string
"""
return o.__module__ + '.' + o.__class__.__name__
def load(string):
""" Given a dotted full module string representation of a class it loads it
Args:
string (str) i.e. 'qubes.storage.xen.QubesXenVmStorage'
Returns:
type
See also:
:func:`qubes.storage.dump`
"""
if not type(string) is str:
# This is a hack which allows giving a real class to a vm instead of a
# string as string_class parameter.
return string
components = string.split(".")
module_path = ".".join(components[:-1])
klass = components[-1:][0]
module = __import__(module_path, fromlist=[klass])
return getattr(module, klass)
def get_pool(name, vm):
""" Instantiates the storage for the specified vm """
config = _get_storage_config_parser()
klass = _get_pool_klass(name, config)
keys = [k for k in config.options(name) if k != 'driver' and k != 'class']
values = [config.get(name, o) for o in keys]
config_kwargs = dict(zip(keys, values))
if name == 'default':
kwargs = defaults['pool_config'].copy()
kwargs.update(keys)
else:
kwargs = config_kwargs
return klass(vm, **kwargs)
def pool_exists(name):
""" Check if the specified pool exists """
try:
_get_pool_klass(name)
return True
except StoragePoolException:
return False
def add_pool(name, **kwargs):
""" Add a storage pool to config."""
config = _get_storage_config_parser()
config.add_section(name)
for key, value in kwargs.iteritems():
config.set(name, key, value)
_write_config(config)
def remove_pool(name):
""" Remove a storage pool from config file. """
config = _get_storage_config_parser()
config.remove_section(name)
_write_config(config)
def _write_config(config):
with open(CONFIG_FILE, 'w') as configfile:
config.write(configfile)
def _get_storage_config_parser():
""" Instantiates a `ConfigParaser` for specified storage config file.
Returns:
RawConfigParser
"""
config = ConfigParser.RawConfigParser()
config.read(CONFIG_FILE)
return config
def _get_pool_klass(name, config=None):
""" Returns the storage klass for the specified pool.
Args:
name: The pool name.
config: If ``config`` is not specified
`_get_storage_config_parser()` is called.
Returns:
type: A class inheriting from `QubesVmStorage`
"""
if config is None:
config = _get_storage_config_parser()
if not config.has_section(name):
raise StoragePoolException('Uknown storage pool ' + name)
if config.has_option(name, 'class'):
klass = load(config.get(name, 'class'))
elif config.has_option(name, 'driver'):
pool_driver = config.get(name, 'driver')
klass = defaults['pool_drivers'][pool_driver]
else:
raise StoragePoolException('Uknown storage pool driver ' + name)
return klass
class StoragePoolException(QubesException):
pass
class Pool(object):
def __init__(self, vm, dir_path):
assert vm is not None
assert dir_path is not None
self.vm = vm
self.dir_path = dir_path
self.create_dir_if_not_exists(self.dir_path)
self.vmdir = self.vmdir_path(vm, self.dir_path)
appvms_path = os.path.join(self.dir_path, 'appvms')
self.create_dir_if_not_exists(appvms_path)
servicevms_path = os.path.join(self.dir_path, 'servicevms')
self.create_dir_if_not_exists(servicevms_path)
vm_templates_path = os.path.join(self.dir_path, 'vm-templates')
self.create_dir_if_not_exists(vm_templates_path)
def vmdir_path(self, vm, pool_dir):
""" Returns the path to vmdir depending on the type of the VM.
The default QubesOS file storage saves the vm images in three
different directories depending on the ``QubesVM`` type:
* ``appvms`` for ``QubesAppVm`` or ``QubesHvm``
* ``vm-templates`` for ``QubesTemplateVm`` or ``QubesTemplateHvm``
* ``servicevms`` for any subclass of ``QubesNetVm``
Args:
vm: a QubesVM
pool_dir: the root directory of the pool
Returns:
string (str) absolute path to the directory where the vm files
are stored
"""
if vm.is_appvm():
subdir = 'appvms'
elif vm.is_template():
subdir = 'vm-templates'
elif vm.is_netvm():
subdir = 'servicevms'
elif vm.is_disposablevm():
subdir = 'appvms'
return os.path.join(pool_dir, subdir, vm.template.name + '-dvm')
else:
raise QubesException(vm.type() + ' unknown vm type')
return os.path.join(pool_dir, subdir, vm.name)
def create_dir_if_not_exists(self, path):
""" Check if a directory exists in if not create it.
This method does not create any parent directories.
"""
if not os.path.exists(path):
os.mkdir(path)

View File

@ -39,7 +39,7 @@ import qubes.storage
import qubes.vm.templatevm import qubes.vm.templatevm
class XenVMStorage(qubes.storage.VMStorage): class XenStorage(qubes.storage.Storage):
'''Class for VM storage of Xen VMs. '''Class for VM storage of Xen VMs.
''' '''
@ -48,10 +48,53 @@ class XenVMStorage(qubes.storage.VMStorage):
volatile_dev = 'xvdc' volatile_dev = 'xvdc'
modules_dev = 'xvdd' modules_dev = 'xvdd'
def __init__(self, vm, vmdir, **kwargs):
""" Instantiate the storage.
Args:
vm: a QubesVM
vmdir: the root directory of the pool
"""
assert vm is not None
assert vmdir is not None
super(XenStorage, self).__init__(vm, **kwargs)
self.vmdir = vmdir
@property
def private_img(self):
'''Path to the private image'''
return self.abspath(qubes.config.vm_files['private_img'])
@property
def root_img(self):
'''Path to the root image'''
return self.vm.template.root_img if hasattr(self.vm, 'template') \
else self.abspath(qubes.config.vm_files['root_img'])
@property
def rootcow_img(self):
'''Path to the root COW image'''
if isinstance(self.vm, qubes.vm.templatevm.TemplateVM):
return self.abspath(qubes.config.vm_files['rootcow_img'])
return None
@property
def volatile_img(self):
'''Path to the volatile image'''
return self.abspath(qubes.config.vm_files['volatile_img'])
# pylint: disable=redefined-builtin # pylint: disable=redefined-builtin
@staticmethod @staticmethod
def _format_disk_dev(path, vdev, script=None, rw=True, type='disk', def format_disk_dev(path, vdev, script=None, rw=True, type='disk',
domain=None): domain=None):
if path is None: if path is None:
return '' return ''
@ -76,52 +119,50 @@ class XenVMStorage(qubes.storage.VMStorage):
return lxml.etree.tostring(element) return lxml.etree.tostring(element)
def _get_rootdev(self): def root_dev_config(self):
if isinstance(self.vm, qubes.vm.templatevm.TemplateVM): if isinstance(self.vm, qubes.vm.templatevm.TemplateVM):
return self._format_disk_dev( return self.format_disk_dev(
'{}:{}'.format(self.root_img, self.vm.rootcow_img), '{root}:{rootcow}'.format(
root=self.root_img,
rootcow=self.rootcow_img),
self.root_dev, self.root_dev,
script='block-origin') script='block-origin')
elif self.vm.hvm and hasattr(self.vm, 'template'):
# HVM template-based VM - only one device-mapper layer, in dom0
# (root+volatile)
# HVM detection based on 'kernel' property is massive hack,
# but taken from assumption that VM needs Qubes-specific kernel
# (actually initramfs) to assemble the second layer of device-mapper
return self.format_disk_dev(
'{root}:{volatile}'.format(
root=self.vm.template.storage.root_img,
volatile=self.volatile_img),
self.root_dev,
script='block-snapshot')
elif hasattr(self.vm, 'template'): elif hasattr(self.vm, 'template'):
return self._format_disk_dev( # any other template-based VM - two device-mapper layers: one
'{}:{}'.format(self.root_img, self.vm.template.rootcow_img), # in dom0 (here) from root+root-cow, and another one from
# this+volatile.img
return self.format_disk_dev(
'{root}:{template_rootcow}'.format(
root=self.root_img,
template_rootcow=self.vm.template.storage.rootcow_img),
self.root_dev, self.root_dev,
script='block-snapshot', script='block-snapshot',
rw=False) rw=False)
else: else:
return self._format_disk_dev(self.root_img, self.root_dev) # standalone qube
return self.format_disk_dev(self.root_img, self.root_dev)
def get_config_params(self): def private_dev_config(self):
args = {} self.format_disk_dev(self.private_img, self.private_dev)
args['rootdev'] = self._get_rootdev()
args['privatedev'] = self._format_disk_dev(self.private_img,
self.private_dev)
args['volatiledev'] = self._format_disk_dev(self.volatile_img,
self.volatile_dev)
args['kerneldir'] = self.kernels_dir def volatile_dev_config(self):
self.format_disk_dev(self.volatile_img, self.volatile_dev)
if self.modules_img is not None:
args['otherdevs'] = self._format_disk_dev(self.modules_img,
self.modules_dev, rw=self.modules_img_rw)
elif self.drive is not None:
(drive_type, drive_domain, drive_path) = self.drive.split(":")
if drive_domain.lower() == "dom0":
drive_domain = None
args['otherdevs'] = self._format_disk_dev(drive_path,
self.modules_dev,
rw=(drive_type == "disk"),
type=drive_type,
domain=drive_domain)
else:
args['otherdevs'] = ''
return args
def create_on_disk_private_img(self, source_template=None): def create_on_disk_private_img(self, source_template=None):
@ -185,3 +226,70 @@ class XenVMStorage(qubes.storage.VMStorage):
f_cow.close() f_cow.close()
f_root.close() f_root.close()
os.umask(old_umask) os.umask(old_umask)
def reset_volatile_storage(self, source_template=None):
if source_template is None:
source_template = self.vm.template
if source_template is not None:
# template-based VM with only one device-mapper layer -
# volatile.img used as upper layer on root.img, no root-cow.img
# intermediate layer
# XXX marmarek says this is either always true or always false;
# rootcow_img got smashed in 35cb82 (#1573)
# this may have remain after HVM check
# this probably should have happen anyway
if not source_template.storage.rootcow_img:
if os.path.exists(self.volatile_img):
if self.vm.debug:
if os.path.getmtime(source_template.storage.root_img) \
> os.path.getmtime(self.volatile_img):
self.vm.log.warning(
'Template have changed, resetting root.img')
else:
self.vm.log.warning(
'Debug mode: not resetting root.img; if you'
' want to force root.img reset, either'
' update template VM, or remove volatile.img'
' file.')
return
os.remove(self.volatile_img)
# FIXME stat on f_root; with open() ...
f_volatile = open(self.volatile_img, "w")
f_root = open(source_template.storage.root_img, "r")
f_root.seek(0, os.SEEK_END)
f_volatile.truncate(f_root.tell()) # make empty sparse file of the same size as root.img
f_volatile.close()
f_root.close()
return # XXX why is that? super() does not run
super(XenStorage, self).reset_volatile_storage(
source_template=source_template)
def prepare_for_vm_startup(self):
super(XenStorage, self).prepare_for_vm_startup()
if self.drive is not None:
(drive_type, drive_domain, drive_path) = self.drive.split(":")
if drive_domain.lower() != "dom0":
# XXX "VM '{}' holding '{}' does not exists".format(
drive_vm = self.vm.app.domains[drive_domain]
if not drive_vm.is_running():
raise qubes.exc.QubesVMNotRunningError(drive_vm,
'VM {!r} holding {!r} isn\'t running'.format(
drive_domain, drive_path))
if self.rootcow_img and not os.path.exists(self.rootcow_img):
self.commit_template_changes()
class XenPool(qubes.storage.Pool):
def get_storage(self):
""" Returns an instantiated ``XenStorage``. """
return XenStorage(self.vm, vmdir=self.vmdir)

View File

@ -238,7 +238,7 @@ class QubesTestCase(unittest.TestCase):
:param xml2: second element :param xml2: second element
:type xml1: :py:class:`lxml.etree._Element` :type xml1: :py:class:`lxml.etree._Element`
:type xml2: :py:class:`lxml.etree._Element` :type xml2: :py:class:`lxml.etree._Element`
''' ''' # pylint: disable=invalid-name
self.assertEqual(xml1.tag, xml2.tag) self.assertEqual(xml1.tag, xml2.tag)
self.assertEqual(xml1.text, xml2.text) self.assertEqual(xml1.text, xml2.text)

View File

@ -26,6 +26,8 @@
import multiprocessing import multiprocessing
import os import os
import shutil import shutil
import subprocess
import tempfile
import unittest import unittest
import time import time
@ -34,6 +36,8 @@ from qubes.qubes import QubesVmCollection, QubesException, system_path
import qubes import qubes
import qubes.vm.qubesvm import qubes.vm.qubesvm
import qubes.tests import qubes.tests
from qubes.qubes import QubesVmLabels
class TC_00_Basic(qubes.tests.SystemTestsMixin, qubes.tests.QubesTestCase): class TC_00_Basic(qubes.tests.SystemTestsMixin, qubes.tests.QubesTestCase):
def setUp(self): def setUp(self):
@ -81,6 +85,12 @@ class TC_01_Properties(qubes.tests.SystemTestsMixin, qubes.tests.QubesTestCase):
newname = self.make_vm_name('newname') newname = self.make_vm_name('newname')
self.assertEqual(self.vm.name, self.vmname) self.assertEqual(self.vm.name, self.vmname)
self.vm.write_firewall_conf({'allow': False, 'allowDns': False})
self.vm.autostart = True
self.addCleanup(os.system,
'sudo systemctl -q disable qubes-vm@{}.service || :'.
format(self.vmname))
pre_rename_firewall = self.vm.get_firewall_conf()
#TODO: change to setting property when implemented #TODO: change to setting property when implemented
self.vm.set_name(newname) self.vm.set_name(newname)
@ -106,6 +116,16 @@ class TC_01_Properties(qubes.tests.SystemTestsMixin, qubes.tests.QubesTestCase):
self.assertFalse(os.path.exists( self.assertFalse(os.path.exists(
os.path.join(os.getenv("HOME"), ".local/share/applications", os.path.join(os.getenv("HOME"), ".local/share/applications",
self.vmname + "-firefox.desktop"))) self.vmname + "-firefox.desktop")))
self.assertEquals(pre_rename_firewall, self.vm.get_firewall_conf())
with self.assertNotRaises((QubesException, OSError)):
self.vm.write_firewall_conf({'allow': False})
self.assertTrue(self.vm.autostart)
self.assertTrue(os.path.exists(
'/etc/systemd/system/multi-user.target.wants/'
'qubes-vm@{}.service'.format(newname)))
self.assertFalse(os.path.exists(
'/etc/systemd/system/multi-user.target.wants/'
'qubes-vm@{}.service'.format(self.vmname)))
def test_010_netvm(self): def test_010_netvm(self):
if self.qc.get_default_netvm() is None: if self.qc.get_default_netvm() is None:
@ -166,6 +186,664 @@ class TC_01_Properties(qubes.tests.SystemTestsMixin, qubes.tests.QubesTestCase):
with self.assertRaises(ValueError): with self.assertRaises(ValueError):
self.vm.dispvm_netvm = self.vm self.vm.dispvm_netvm = self.vm
def test_030_clone(self):
testvm1 = self.qc.add_new_vm(
"QubesAppVm",
name=self.make_vm_name("vm"),
template=self.qc.get_default_template())
testvm1.create_on_disk(verbose=False)
testvm2 = self.qc.add_new_vm(testvm1.__class__.__name__,
name=self.make_vm_name("clone"),
template=testvm1.template,
)
testvm2.clone_attrs(src_vm=testvm1)
testvm2.clone_disk_files(src_vm=testvm1, verbose=False)
# qubes.xml reload
self.save_and_reload_db()
testvm1 = self.qc[testvm1.qid]
testvm2 = self.qc[testvm2.qid]
self.assertEquals(testvm1.label, testvm2.label)
self.assertEquals(testvm1.netvm, testvm2.netvm)
self.assertEquals(testvm1.uses_default_netvm,
testvm2.uses_default_netvm)
self.assertEquals(testvm1.kernel, testvm2.kernel)
self.assertEquals(testvm1.kernelopts, testvm2.kernelopts)
self.assertEquals(testvm1.uses_default_kernel,
testvm2.uses_default_kernel)
self.assertEquals(testvm1.uses_default_kernelopts,
testvm2.uses_default_kernelopts)
self.assertEquals(testvm1.memory, testvm2.memory)
self.assertEquals(testvm1.maxmem, testvm2.maxmem)
self.assertEquals(testvm1.pcidevs, testvm2.pcidevs)
self.assertEquals(testvm1.include_in_backups,
testvm2.include_in_backups)
self.assertEquals(testvm1.default_user, testvm2.default_user)
self.assertEquals(testvm1.services, testvm2.services)
self.assertEquals(testvm1.get_firewall_conf(),
testvm2.get_firewall_conf())
# now some non-default values
testvm1.netvm = None
testvm1.uses_default_netvm = False
testvm1.label = QubesVmLabels['orange']
testvm1.memory = 512
firewall = testvm1.get_firewall_conf()
firewall['allowDns'] = False
firewall['allowYumProxy'] = False
firewall['rules'] = [{'address': '1.2.3.4',
'netmask': 24,
'proto': 'tcp',
'portBegin': 22,
'portEnd': 22,
}]
testvm1.write_firewall_conf(firewall)
testvm3 = self.qc.add_new_vm(testvm1.__class__.__name__,
name=self.make_vm_name("clone2"),
template=testvm1.template,
)
testvm3.clone_attrs(src_vm=testvm1)
testvm3.clone_disk_files(src_vm=testvm1, verbose=False)
# qubes.xml reload
self.save_and_reload_db()
testvm1 = self.qc[testvm1.qid]
testvm3 = self.qc[testvm3.qid]
self.assertEquals(testvm1.label, testvm3.label)
self.assertEquals(testvm1.netvm, testvm3.netvm)
self.assertEquals(testvm1.uses_default_netvm,
testvm3.uses_default_netvm)
self.assertEquals(testvm1.kernel, testvm3.kernel)
self.assertEquals(testvm1.kernelopts, testvm3.kernelopts)
self.assertEquals(testvm1.uses_default_kernel,
testvm3.uses_default_kernel)
self.assertEquals(testvm1.uses_default_kernelopts,
testvm3.uses_default_kernelopts)
self.assertEquals(testvm1.memory, testvm3.memory)
self.assertEquals(testvm1.maxmem, testvm3.maxmem)
self.assertEquals(testvm1.pcidevs, testvm3.pcidevs)
self.assertEquals(testvm1.include_in_backups,
testvm3.include_in_backups)
self.assertEquals(testvm1.default_user, testvm3.default_user)
self.assertEquals(testvm1.services, testvm3.services)
self.assertEquals(testvm1.get_firewall_conf(),
testvm3.get_firewall_conf())
class TC_02_QvmPrefs(qubes.tests.SystemTestsMixin, qubes.tests.QubesTestCase):
def setup_appvm(self):
self.testvm = self.qc.add_new_vm(
"QubesAppVm",
name=self.make_vm_name("vm"),
template=self.qc.get_default_template())
self.testvm.create_on_disk(verbose=False)
self.save_and_reload_db()
self.qc.unlock_db()
def setup_hvm(self):
self.testvm = self.qc.add_new_vm(
"QubesHVm",
name=self.make_vm_name("hvm"))
self.testvm.create_on_disk(verbose=False)
self.save_and_reload_db()
self.qc.unlock_db()
def pref_set(self, name, value, valid=True):
p = subprocess.Popen(
['qvm-prefs', '-s', '--', self.testvm.name, name, value],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
(stdout, stderr) = p.communicate()
if valid:
self.assertEquals(p.returncode, 0,
"qvm-prefs -s .. '{}' '{}' failed: {}{}".format(
name, value, stdout, stderr
))
else:
self.assertNotEquals(p.returncode, 0,
"qvm-prefs should reject value '{}' for "
"property '{}'".format(value, name))
def pref_get(self, name):
p = subprocess.Popen(['qvm-prefs', '-g', self.testvm.name, name],
stdout=subprocess.PIPE)
(stdout, _) = p.communicate()
self.assertEquals(p.returncode, 0)
return stdout.strip()
bool_test_values = [
('true', 'True', True),
('False', 'False', True),
('0', 'False', True),
('1', 'True', True),
('invalid', '', False)
]
def execute_tests(self, name, values):
"""
Helper function, which executes tests for given property.
:param values: list of tuples (value, expected, valid),
where 'value' is what should be set and 'expected' is what should
qvm-prefs returns as a property value and 'valid' marks valid and
invalid values - if it's False, qvm-prefs should reject the value
:return: None
"""
for (value, expected, valid) in values:
self.pref_set(name, value, valid)
if valid:
self.assertEquals(self.pref_get(name), expected)
def test_000_kernel(self):
self.setup_appvm()
default_kernel = self.qc.get_default_kernel()
self.execute_tests('kernel', [
('default', default_kernel, True),
(default_kernel, default_kernel, True),
('invalid', '', False),
])
def test_001_include_in_backups(self):
self.setup_appvm()
self.execute_tests('include_in_backups', self.bool_test_values)
def test_002_qrexec_timeout(self):
self.setup_appvm()
self.execute_tests('qrexec_timeout', [
('60', '60', True),
('0', '0', True),
('-10', '', False),
('invalid', '', False)
])
def test_003_internal(self):
self.setup_appvm()
self.execute_tests('include_in_backups', self.bool_test_values)
def test_004_label(self):
self.setup_appvm()
self.execute_tests('label', [
('red', 'red', True),
('blue', 'blue', True),
('amber', '', False),
])
def test_005_kernelopts(self):
self.setup_appvm()
self.execute_tests('kernelopts', [
('option', 'option', True),
('default', 'nopat', True),
('', '', True),
])
def test_006_template(self):
templates = [tpl for tpl in self.qc.values() if tpl.is_template()]
if not templates:
self.skip("No templates installed")
some_template = templates[0].name
self.setup_appvm()
self.execute_tests('template', [
(some_template, some_template, True),
('invalid', '', False),
])
def test_007_memory(self):
self.setup_appvm()
qh = qubes.qubes.QubesHost()
memory_total = qh.memory_total
self.execute_tests('memory', [
('300', '300', True),
('1500', '1500', True),
# TODO:
#('500M', '500', True),
#(str(self.testvm.maxmem+500), '', False),
(str(2*memory_total), '', False),
])
def test_008_maxmem(self):
self.setup_appvm()
qh = qubes.qubes.QubesHost()
memory_total = qh.memory_total
self.execute_tests('memory', [
('300', '300', True),
('1500', '1500', True),
# TODO:
#('500M', '500', True),
#(str(self.testvm.memory-50), '', False),
(str(2*memory_total), '', False),
])
def test_009_autostart(self):
self.setup_appvm()
self.execute_tests('autostart', self.bool_test_values)
def test_010_pci_strictreset(self):
self.setup_appvm()
self.execute_tests('pci_strictreset', self.bool_test_values)
def test_011_dispvm_netvm(self):
self.setup_appvm()
default_netvm = self.qc.get_default_netvm().name
netvms = [tpl for tpl in self.qc.values() if tpl.is_netvm()]
if not netvms:
self.skip("No netvms installed")
some_netvm = netvms[0].name
if some_netvm == default_netvm:
if len(netvms) <= 1:
self.skip("At least two NetVM/ProxyVM required")
some_netvm = netvms[1].name
self.execute_tests('dispvm_netvm', [
(some_netvm, some_netvm, True),
(default_netvm, default_netvm, True),
('default', default_netvm, True),
('none', '', True),
(self.testvm.name, '', False),
('invalid', '', False)
])
def test_012_mac(self):
self.setup_appvm()
default_mac = self.testvm.mac
self.execute_tests('mac', [
('00:11:22:33:44:55', '00:11:22:33:44:55', True),
('auto', default_mac, True),
# TODO:
#('00:11:22:33:44:55:66', '', False),
('invalid', '', False),
])
def test_013_default_user(self):
self.setup_appvm()
self.execute_tests('default_user', [
('someuser', self.testvm.template.default_user, True)
# TODO: tests for standalone VMs
])
def test_014_pcidevs(self):
self.setup_appvm()
self.execute_tests('pcidevs', [
('[]', '[]', True),
('[ "00:00.0" ]', "['00:00.0']", True),
('invalid', '', False),
('[invalid]', '', False),
# TODO:
#('["12:12.0"]', '', False)
])
def test_015_name(self):
self.setup_appvm()
self.execute_tests('name', [
('invalid!@#name', '', False),
# TODO: duplicate name test - would fail for now...
])
newname = self.make_vm_name('newname')
self.pref_set('name', newname, True)
self.qc.lock_db_for_reading()
self.qc.load()
self.qc.unlock_db()
self.testvm = self.qc.get_vm_by_name(newname)
self.assertEquals(self.pref_get('name'), newname)
def test_016_vcpus(self):
self.setup_appvm()
self.execute_tests('vcpus', [
('1', '1', True),
('100', '', False),
('-1', '', False),
('invalid', '', False),
])
def test_017_debug(self):
self.setup_appvm()
self.execute_tests('debug', [
('on', 'True', True),
('off', 'False', True),
('true', 'True', True),
('0', 'False', True),
('invalid', '', False)
])
def test_018_netvm(self):
self.setup_appvm()
default_netvm = self.qc.get_default_netvm().name
netvms = [tpl for tpl in self.qc.values() if tpl.is_netvm()]
if not netvms:
self.skip("No netvms installed")
some_netvm = netvms[0].name
if some_netvm == default_netvm:
if len(netvms) <= 1:
self.skip("At least two NetVM/ProxyVM required")
some_netvm = netvms[1].name
self.execute_tests('netvm', [
(some_netvm, some_netvm, True),
(default_netvm, default_netvm, True),
('default', default_netvm, True),
('none', '', True),
(self.testvm.name, '', False),
('invalid', '', False)
])
def test_019_guiagent_installed(self):
self.setup_hvm()
self.execute_tests('guiagent_installed', self.bool_test_values)
def test_020_qrexec_installed(self):
self.setup_hvm()
self.execute_tests('qrexec_installed', self.bool_test_values)
def test_021_seamless_gui_mode(self):
self.setup_hvm()
# should reject seamless mode without gui agent
self.execute_tests('seamless_gui_mode', [
('True', '', False),
('False', 'False', True),
])
self.execute_tests('guiagent_installed', [('True', 'True', True)])
self.execute_tests('seamless_gui_mode', self.bool_test_values)
def test_022_drive(self):
self.setup_hvm()
self.execute_tests('drive', [
('hd:dom0:/tmp/drive.img', 'hd:dom0:/tmp/drive.img', True),
('hd:/tmp/drive.img', 'hd:dom0:/tmp/drive.img', True),
('cdrom:dom0:/tmp/drive.img', 'cdrom:dom0:/tmp/drive.img', True),
('cdrom:/tmp/drive.img', 'cdrom:dom0:/tmp/drive.img', True),
('/tmp/drive.img', 'cdrom:dom0:/tmp/drive.img', True),
('hd:drive.img', '', False),
('drive.img', '', False),
])
def test_023_timezone(self):
self.setup_hvm()
self.execute_tests('timezone', [
('localtime', 'localtime', True),
('0', '0', True),
('3600', '3600', True),
('-7200', '-7200', True),
('invalid', '', False),
])
def test_024_pv_reject_hvm_props(self):
self.setup_appvm()
self.execute_tests('guiagent_installed', [('False', '', False)])
self.execute_tests('qrexec_installed', [('False', '', False)])
self.execute_tests('drive', [('/tmp/drive.img', '', False)])
self.execute_tests('timezone', [('localtime', '', False)])
def test_025_hvm_reject_pv_props(self):
self.setup_hvm()
self.execute_tests('kernel', [('default', '', False)])
self.execute_tests('kernelopts', [('default', '', False)])
class TC_03_QvmRevertTemplateChanges(qubes.tests.SystemTestsMixin,
qubes.tests.QubesTestCase):
def setup_pv_template(self):
self.test_template = self.qc.add_new_vm(
"QubesTemplateVm",
name=self.make_vm_name("pv-clone"),
)
self.test_template.clone_attrs(src_vm=self.qc.get_default_template())
self.test_template.clone_disk_files(
src_vm=self.qc.get_default_template(),
verbose=False)
self.save_and_reload_db()
self.qc.unlock_db()
def setup_hvm_template(self):
self.test_template = self.qc.add_new_vm(
"QubesTemplateHVm",
name=self.make_vm_name("hvm"),
)
self.test_template.create_on_disk(verbose=False)
self.save_and_reload_db()
self.qc.unlock_db()
def get_rootimg_checksum(self):
p = subprocess.Popen(['sha1sum', self.test_template.root_img],
stdout=subprocess.PIPE)
return p.communicate()[0]
def _do_test(self):
checksum_before = self.get_rootimg_checksum()
self.test_template.start(verbose=False)
self.shutdown_and_wait(self.test_template)
checksum_changed = self.get_rootimg_checksum()
if checksum_before == checksum_changed:
self.log.warning("template not modified, test result will be "
"unreliable")
with self.assertNotRaises(subprocess.CalledProcessError):
subprocess.check_call(['sudo', 'qvm-revert-template-changes',
'--force', self.test_template.name])
checksum_after = self.get_rootimg_checksum()
self.assertEquals(checksum_before, checksum_after)
def test_000_revert_pv(self):
"""
Test qvm-revert-template-changes for PV template
"""
self.setup_pv_template()
self._do_test()
def test_000_revert_hvm(self):
"""
Test qvm-revert-template-changes for HVM template
"""
# TODO: have some system there, so the root.img will get modified
self.setup_hvm_template()
self._do_test()
class TC_04_DispVM(qubes.tests.SystemTestsMixin,
qubes.tests.QubesTestCase):
@staticmethod
def get_dispvm_template_name():
vmdir = os.readlink('/var/lib/qubes/dvmdata/vmdir')
return os.path.basename(vmdir)
def test_000_firewall_propagation(self):
"""
Check firewall propagation VM->DispVM, when VM have some firewall rules
"""
# FIXME: currently qubes.xml doesn't contain this information...
dispvm_template_name = self.get_dispvm_template_name()
dispvm_template = self.qc.get_vm_by_name(dispvm_template_name)
testvm1 = self.qc.add_new_vm("QubesAppVm",
name=self.make_vm_name('vm1'),
template=self.qc.get_default_template())
testvm1.create_on_disk(verbose=False)
firewall = testvm1.get_firewall_conf()
firewall['allowDns'] = False
firewall['allowYumProxy'] = False
firewall['rules'] = [{'address': '1.2.3.4',
'netmask': 24,
'proto': 'tcp',
'portBegin': 22,
'portEnd': 22,
}]
testvm1.write_firewall_conf(firewall)
self.qc.save()
self.qc.unlock_db()
testvm1.start()
p = testvm1.run("qvm-run --dispvm 'qubesdb-read /name; echo ERROR;"
" read x'",
passio_popen=True)
dispvm_name = p.stdout.readline().strip()
self.qc.lock_db_for_reading()
self.qc.load()
self.qc.unlock_db()
dispvm = self.qc.get_vm_by_name(dispvm_name)
self.assertIsNotNone(dispvm, "DispVM {} not found in qubes.xml".format(
dispvm_name))
# check if firewall was propagated to the DispVM
self.assertEquals(testvm1.get_firewall_conf(),
dispvm.get_firewall_conf())
# and only there (#1608)
self.assertNotEquals(dispvm_template.get_firewall_conf(),
dispvm.get_firewall_conf())
# then modify some rule
firewall = dispvm.get_firewall_conf()
firewall['rules'] = [{'address': '4.3.2.1',
'netmask': 24,
'proto': 'tcp',
'portBegin': 22,
'portEnd': 22,
}]
dispvm.write_firewall_conf(firewall)
# and check again if wasn't saved anywhere else (#1608)
self.assertNotEquals(dispvm_template.get_firewall_conf(),
dispvm.get_firewall_conf())
self.assertNotEquals(testvm1.get_firewall_conf(),
dispvm.get_firewall_conf())
p.stdin.write('\n')
p.wait()
def test_001_firewall_propagation(self):
"""
Check firewall propagation VM->DispVM, when VM have no firewall rules
"""
testvm1 = self.qc.add_new_vm("QubesAppVm",
name=self.make_vm_name('vm1'),
template=self.qc.get_default_template())
testvm1.create_on_disk(verbose=False)
self.qc.save()
self.qc.unlock_db()
# FIXME: currently qubes.xml doesn't contain this information...
dispvm_template_name = self.get_dispvm_template_name()
dispvm_template = self.qc.get_vm_by_name(dispvm_template_name)
original_firewall = None
if os.path.exists(dispvm_template.firewall_conf):
original_firewall = tempfile.TemporaryFile()
with open(dispvm_template.firewall_conf) as f:
original_firewall.write(f.read())
try:
firewall = dispvm_template.get_firewall_conf()
firewall['allowDns'] = False
firewall['allowYumProxy'] = False
firewall['rules'] = [{'address': '1.2.3.4',
'netmask': 24,
'proto': 'tcp',
'portBegin': 22,
'portEnd': 22,
}]
dispvm_template.write_firewall_conf(firewall)
testvm1.start()
p = testvm1.run("qvm-run --dispvm 'qubesdb-read /name; echo ERROR;"
" read x'",
passio_popen=True)
dispvm_name = p.stdout.readline().strip()
self.qc.lock_db_for_reading()
self.qc.load()
self.qc.unlock_db()
dispvm = self.qc.get_vm_by_name(dispvm_name)
self.assertIsNotNone(dispvm, "DispVM {} not found in qubes.xml".format(
dispvm_name))
# check if firewall was propagated to the DispVM from the right VM
self.assertEquals(testvm1.get_firewall_conf(),
dispvm.get_firewall_conf())
# and only there (#1608)
self.assertNotEquals(dispvm_template.get_firewall_conf(),
dispvm.get_firewall_conf())
# then modify some rule
firewall = dispvm.get_firewall_conf()
firewall['rules'] = [{'address': '4.3.2.1',
'netmask': 24,
'proto': 'tcp',
'portBegin': 22,
'portEnd': 22,
}]
dispvm.write_firewall_conf(firewall)
# and check again if wasn't saved anywhere else (#1608)
self.assertNotEquals(dispvm_template.get_firewall_conf(),
dispvm.get_firewall_conf())
self.assertNotEquals(testvm1.get_firewall_conf(),
dispvm.get_firewall_conf())
p.stdin.write('\n')
p.wait()
finally:
if original_firewall:
original_firewall.seek(0)
with open(dispvm_template.firewall_conf, 'w') as f:
f.write(original_firewall.read())
original_firewall.close()
else:
os.unlink(dispvm_template.firewall_conf)
def test_002_cleanup(self):
self.qc.unlock_db()
p = subprocess.Popen(['/usr/lib/qubes/qfile-daemon-dvm',
'qubes.VMShell', 'dom0', 'DEFAULT'],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=open(os.devnull, 'w'))
(stdout, _) = p.communicate(input="echo test; qubesdb-read /name; "
"echo ERROR\n")
self.assertEquals(p.returncode, 0)
lines = stdout.splitlines()
self.assertEqual(lines[0], "test")
dispvm_name = lines[1]
self.qc.lock_db_for_reading()
self.qc.load()
self.qc.unlock_db()
dispvm = self.qc.get_vm_by_name(dispvm_name)
self.assertIsNone(dispvm, "DispVM {} still exists in qubes.xml".format(
dispvm_name))
def test_003_cleanup_destroyed(self):
"""
Check if DispVM is properly removed even if it terminated itself (#1660)
:return:
"""
self.qc.unlock_db()
p = subprocess.Popen(['/usr/lib/qubes/qfile-daemon-dvm',
'qubes.VMShell', 'dom0', 'DEFAULT'],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=open(os.devnull, 'w'))
p.stdin.write("qubesdb-read /name\n")
p.stdin.write("echo ERROR\n")
p.stdin.write("poweroff\n")
# do not close p.stdin on purpose - wait to automatic disconnect when
# domain is destroyed
timeout = 30
while timeout > 0:
if p.poll():
break
time.sleep(1)
timeout -= 1
# includes check for None - timeout
self.assertEquals(p.returncode, 0)
lines = p.stdout.read().splitlines()
dispvm_name = lines[0]
self.assertNotEquals(dispvm_name, "ERROR")
self.qc.lock_db_for_reading()
self.qc.load()
self.qc.unlock_db()
dispvm = self.qc.get_vm_by_name(dispvm_name)
self.assertIsNone(dispvm, "DispVM {} still exists in qubes.xml".format(
dispvm_name))
# vim: ts=4 sw=4 et # vim: ts=4 sw=4 et

View File

@ -42,6 +42,15 @@ LOG_PATH = '/var/log/qubes/qmemman.log'
system_state = qubes.qmemman.SystemState() system_state = qubes.qmemman.SystemState()
global_lock = thread.allocate_lock() global_lock = thread.allocate_lock()
# If XS_Watcher will
# handle meminfo event before @introduceDomain, it will use
# incomplete domain list for that and may redistribute memory
# allocated to some VM, but not yet used (see #1389).
# To fix that, system_state should be updated (refresh domain
# list) before processing other changes, every time some process requested
# memory for a new VM, before releasing the lock. Then XS_Watcher will check
# this flag before processing other event.
force_refresh_domain_list = False
def only_in_first_list(l1, l2): def only_in_first_list(l1, l2):
ret = [] ret = []
@ -65,41 +74,65 @@ class XS_Watcher(object):
self.log.debug('XS_Watcher()') self.log.debug('XS_Watcher()')
self.handle = xen.lowlevel.xs.xs() self.handle = xen.lowlevel.xs.xs()
self.handle.watch('@introduceDomain', WatchType(XS_Watcher.domain_list_changed, None)) self.handle.watch('@introduceDomain', WatchType(
self.handle.watch('@releaseDomain', WatchType(XS_Watcher.domain_list_changed, None)) XS_Watcher.domain_list_changed, False))
self.handle.watch('@releaseDomain', WatchType(
XS_Watcher.domain_list_changed, False))
self.watch_token_dict = {} self.watch_token_dict = {}
def domain_list_changed(self, refresh_only=False):
"""
Check if any domain was created/destroyed. If it was, update
appropriate list. Then redistribute memory.
def domain_list_changed(self, param): :param refresh_only If True, only refresh domain list, do not
self.log.debug('domain_list_changed(param={!r})'.format(param)) redistribute memory. In this mode, caller must already hold
global_lock.
"""
self.log.debug('domain_list_changed(only_refresh={!r})'.format(
refresh_only))
curr = self.handle.ls('', '/local/domain') got_lock = False
self.log.debug('curr={!r}'.format(curr)) if not refresh_only:
self.log.debug('acquiring global_lock')
global_lock.acquire()
got_lock = True
self.log.debug('global_lock acquired')
try:
curr = self.handle.ls('', '/local/domain')
if curr is None:
return
if curr == None: # check if domain is really there, it may happen that some empty
return # directories are left in xenstore
curr = filter(
lambda x:
self.handle.read('',
'/local/domain/{}/domid'.format(x)
) is not None,
curr
)
self.log.debug('curr={!r}'.format(curr))
self.log.debug('acquiring global_lock') for i in only_in_first_list(curr, self.watch_token_dict.keys()):
global_lock.acquire() # new domain has been created
self.log.debug('global_lock acquired') watch = WatchType(XS_Watcher.meminfo_changed, i)
self.watch_token_dict[i] = watch
self.handle.watch(get_domain_meminfo_key(i), watch)
system_state.add_domain(i)
for i in only_in_first_list(curr, self.watch_token_dict.keys()): for i in only_in_first_list(self.watch_token_dict.keys(), curr):
#new domain has been created # domain destroyed
watch = WatchType(XS_Watcher.meminfo_changed, i) self.handle.unwatch(get_domain_meminfo_key(i), self.watch_token_dict[i])
self.watch_token_dict[i] = watch self.watch_token_dict.pop(i)
self.handle.watch(get_domain_meminfo_key(i), watch) system_state.del_domain(i)
system_state.add_domain(i) finally:
if got_lock:
global_lock.release()
self.log.debug('global_lock released')
for i in only_in_first_list(self.watch_token_dict.keys(), curr): if not refresh_only:
#domain destroyed system_state.do_balance()
self.handle.unwatch(get_domain_meminfo_key(i), self.watch_token_dict[i])
self.watch_token_dict.pop(i)
system_state.del_domain(i)
global_lock.release()
self.log.debug('global_lock released')
system_state.do_balance()
def meminfo_changed(self, domain_id): def meminfo_changed(self, domain_id):
@ -112,6 +145,8 @@ class XS_Watcher(object):
self.log.debug('acquiring global_lock') self.log.debug('acquiring global_lock')
global_lock.acquire() global_lock.acquire()
self.log.debug('global_lock acquired') self.log.debug('global_lock acquired')
if force_refresh_domain_list:
self.domain_list_changed(refresh_only=True)
system_state.refresh_meminfo(domain_id, untrusted_meminfo_key) system_state.refresh_meminfo(domain_id, untrusted_meminfo_key)
@ -148,6 +183,8 @@ class QMemmanReqHandler(SocketServer.BaseRequestHandler):
if len(self.data) == 0: if len(self.data) == 0:
self.log.info('EOF') self.log.info('EOF')
if got_lock: if got_lock:
global force_refresh_domain_list
force_refresh_domain_list = True
global_lock.release() global_lock.release()
self.log.debug('global_lock released') self.log.debug('global_lock released')
return return

View File

@ -46,6 +46,10 @@ parser.add_argument('--property', '--prop', '-p',
action=qubes.tools.PropertyAction, action=qubes.tools.PropertyAction,
help='set domain\'s property, like "internal", "memory" or "vcpus"') help='set domain\'s property, like "internal", "memory" or "vcpus"')
parser.add_argument('--pool-name', '--pool', '-P',
action=qubes.tools.SinglePropertyAction,
help='specify the storage pool to use')
parser.add_argument('--template', '-t', parser.add_argument('--template', '-t',
action=qubes.tools.SinglePropertyAction, action=qubes.tools.SinglePropertyAction,
help='specify the TemplateVM to use') help='specify the TemplateVM to use')

View File

@ -89,6 +89,10 @@ parser.add_argument('--no-start-guid',
action='store_false', dest='start_guid', default=True, action='store_false', dest='start_guid', default=True,
help='do not start the gui daemon (ignored)') help='do not start the gui daemon (ignored)')
parser.add_argument('--skip-if-running',
action='store_true', default=False,
help='Do not fail if the qube is already runnning')
#parser.add_option ("--tray", action="store_true", dest="tray", default=False, #parser.add_option ("--tray", action="store_true", dest="tray", default=False,
# help="Use tray notifications instead of stdout" ) # help="Use tray notifications instead of stdout" )
@ -108,6 +112,9 @@ def main(args=None):
vm = args.vm vm = args.vm
if args.skip_if_running and vm.is_running():
return
if args.drive is not None: if args.drive is not None:
if 'drive' not in (prop.__name__ for prop in vm.property_list()): if 'drive' not in (prop.__name__ for prop in vm.property_list()):
parser.error( parser.error(

View File

@ -57,8 +57,8 @@ def get_timezone():
return None return None
if tz_info.st_nlink > 1: if tz_info.st_nlink > 1:
p = subprocess.Popen(['find', '/usr/share/zoneinfo', p = subprocess.Popen(['find', '/usr/share/zoneinfo',
'-inum', str(tz_info.st_ino)], '-inum', str(tz_info.st_ino), '-print', '-quit'],
stdout=subprocess.PIPE) stdout=subprocess.PIPE)
tz_path = p.communicate()[0].strip() tz_path = p.communicate()[0].strip()
return tz_path.replace('/usr/share/zoneinfo/', '') return tz_path.replace('/usr/share/zoneinfo/', '')
return None return None
@ -104,3 +104,9 @@ def parse_size(size):
return int(size)*multiplier return int(size)*multiplier
raise qubes.exc.QubesException("Invalid size: {0}.".format(size)) raise qubes.exc.QubesException("Invalid size: {0}.".format(size))
def urandom(size):
rand = os.urandom(size)
if rand is None:
raise IOError('failed to read urandom')
return hashlib.sha512(rand).digest()

View File

@ -341,8 +341,10 @@ class BaseVM(qubes.PropertyHolder):
args['netmask'] = self.netmask args['netmask'] = self.netmask
args['netdev'] = lxml.etree.tostring( args['netdev'] = lxml.etree.tostring(
self.lvxml_net_dev(self.ip, self.mac, self.netvm)) self.lvxml_net_dev(self.ip, self.mac, self.netvm))
args['disable_network1'] = '' args['network_begin'] = ''
args['disable_network2'] = '' args['network_end'] = ''
args['no_network_begin'] = '<!--'
args['no_network_end'] = '-->'
else: else:
args['ip'] = '' args['ip'] = ''
args['mac'] = '' args['mac'] = ''
@ -351,8 +353,10 @@ class BaseVM(qubes.PropertyHolder):
args['dns2'] = '' args['dns2'] = ''
args['netmask'] = '' args['netmask'] = ''
args['netdev'] = '' args['netdev'] = ''
args['disable_network1'] = '<!--' args['network_begin'] = '<!--'
args['disable_network2'] = '-->' args['network_end'] = '-->'
args['no_network_begin'] = ''
args['no_network_end'] = ''
args.update(self.storage.get_config_params()) args.update(self.storage.get_config_params())

View File

@ -254,6 +254,7 @@ class NetVMMixin(object):
@qubes.events.handler('property-set:netvm') @qubes.events.handler('property-set:netvm')
def on_property_set_netvm(self, event, name, new_netvm, old_netvm=None): def on_property_set_netvm(self, event, name, new_netvm, old_netvm=None):
# pylint: disable=unused-argument # pylint: disable=unused-argument
# TODO offline_mode
if self.is_running() and new_netvm is not None \ if self.is_running() and new_netvm is not None \
and not new_netvm.is_running(): and not new_netvm.is_running():
raise qubes.exc.QubesVMNotStartedError(new_netvm, raise qubes.exc.QubesVMNotStartedError(new_netvm,

View File

@ -75,6 +75,9 @@ def _setter_name(self, prop, value):
if len(value) > 31: if len(value) > 31:
raise ValueError('{} value must be shorter than 32 characters'.format( raise ValueError('{} value must be shorter than 32 characters'.format(
prop.__name__)) prop.__name__))
# this regexp does not contain '+'; if it had it, we should specifically
# disallow 'lost+found' #1440
if re.match(r"^[a-zA-Z][a-zA-Z0-9_-]*$", value) is None: if re.match(r"^[a-zA-Z][a-zA-Z0-9_-]*$", value) is None:
raise ValueError('{} value contains illegal characters'.format( raise ValueError('{} value contains illegal characters'.format(
prop.__name__)) prop.__name__))
@ -101,7 +104,7 @@ def _setter_kernel(self, prop, value):
if not os.path.exists(dirname): if not os.path.exists(dirname):
raise qubes.exc.QubesPropertyValueError(self, prop, value, raise qubes.exc.QubesPropertyValueError(self, prop, value,
'Kernel {!r} not installed'.format(value)) 'Kernel {!r} not installed'.format(value))
for filename in ('vmlinuz', 'modules.img'): for filename in ('vmlinuz', 'initramfs'):
if not os.path.exists(os.path.join(dirname, filename)): if not os.path.exists(os.path.join(dirname, filename)):
raise qubes.exc.QubesPropertyValueError( raise qubes.exc.QubesPropertyValueError(
'Kernel {!r} not properly installed: missing {!r} file'.format( 'Kernel {!r} not properly installed: missing {!r} file'.format(
@ -119,10 +122,6 @@ def _setter_label(self, prop, value):
return self.app.get_label(value) return self.app.get_label(value)
def _default_conf_file(self, name=None):
return (name or self.name) + '.conf'
class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM): class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
'''Base functionality of Qubes VM shared between all VMs.''' '''Base functionality of Qubes VM shared between all VMs.'''
@ -162,11 +161,6 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
ls_width=36, ls_width=36,
doc='UUID from libvirt.') doc='UUID from libvirt.')
conf_file = qubes.property('conf_file', type=str,
default=_default_conf_file,
saver=(lambda self, prop, value: self.relative_path(value)),
doc='XXX libvirt config file?')
# XXX this should be part of qubes.xml # XXX this should be part of qubes.xml
firewall_conf = qubes.property('firewall_conf', type=str, firewall_conf = qubes.property('firewall_conf', type=str,
default='firewall.xml') default='firewall.xml')
@ -196,6 +190,13 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
ls_width=2, ls_width=2,
doc='FIXME') doc='FIXME')
pool_name = qubes.property('pool_name',
default='default',
doc='storage pool for this qube devices')
dir_path = property((lambda self: self.storage.vmdir),
doc='Root directory for files related to this domain')
# XXX swallowed uses_default_kernel # XXX swallowed uses_default_kernel
# XXX not applicable to HVM? # XXX not applicable to HVM?
kernel = qubes.property('kernel', type=str, kernel = qubes.property('kernel', type=str,
@ -209,6 +210,7 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
kernelopts = qubes.property('kernelopts', type=str, load_stage=4, kernelopts = qubes.property('kernelopts', type=str, load_stage=4,
default=(lambda self: qubes.config.defaults['kernelopts_pcidevs'] \ default=(lambda self: qubes.config.defaults['kernelopts_pcidevs'] \
if len(self.devices['pci']) > 0 \ if len(self.devices['pci']) > 0 \
else self.template.kernelopts if hasattr(self, 'template') \
else qubes.config.defaults['kernelopts']), else qubes.config.defaults['kernelopts']),
ls_width=30, ls_width=30,
doc='Kernel command line passed to domain.') doc='Kernel command line passed to domain.')
@ -291,7 +293,15 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
if self.libvirt_domain is None: if self.libvirt_domain is None:
return -1 return -1
return self.libvirt_domain.ID() try:
return self.libvirt_domain.ID()
except libvirt.libvirtError as e:
if e.get_error_code() == libvirt.VIR_ERR_NO_DOMAIN:
return -1
else:
self.log.exception('libvirt error code: {!r}'.format(
e.get_error_code()))
raise
@property @property
@ -327,16 +337,6 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
return self._qdb_connection return self._qdb_connection
def _get_dir_path(self, name=None):
return os.path.join(
qubes.config.system_path['qubes_base_dir'],
self.dir_path_prefix,
name if name is not None else self.name)
dir_path = property(_get_dir_path,
doc='Root directory for files related to this domain')
# XXX this should go to to AppVM? # XXX this should go to to AppVM?
@property @property
def private_img(self): def private_img(self):
@ -367,14 +367,13 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
@property @property
def uses_custom_config(self): def icon_path(self):
'''True if this machine has config in non-standard place.''' return os.path.join(self.dir_path, 'icon.png')
return not self.property_is_default('conf_file')
# return self.conf_file != self.storage.abspath(self.name + '.conf')
@property @property
def icon_path(self): def conf_file(self):
return os.path.join(self.dir_path, "icon.png") return os.path.join(self.dir_path, self.name + '.conf')
# XXX I don't know what to do with these; probably should be isinstance(...) # XXX I don't know what to do with these; probably should be isinstance(...)
@ -444,8 +443,15 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
# Always set if meminfo-writer should be active or not # Always set if meminfo-writer should be active or not
self.services['meminfo-writer'] = True self.services['meminfo-writer'] = True
if xml is None:
# new qube, disable updates check if requested for new qubes
# TODO: when features (#1637) are done, migrate to plugin
if not self.app.check_updates_vm:
self.services['qubes-update-check'] = False
# Initialize VM image storage class # Initialize VM image storage class
self.storage = qubes.storage.get_storage(self) self.storage = qubes.storage.get_pool(
self.pool_name, self).get_storage(self)
# fire hooks # fire hooks
if xml is None: if xml is None:
@ -483,11 +489,16 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
@qubes.events.handler('property-pre-set:name') @qubes.events.handler('property-pre-set:name')
def on_property_pre_set_name(self, event, name, newvalue, oldvalue=None): def on_property_pre_set_name(self, event, name, newvalue, oldvalue=None):
# pylint: disable=unused-argument # pylint: disable=unused-argument
# TODO not self.is_stopped() would be more appropriate # TODO not self.is_stopped() would be more appropriate
if self.is_running(): if self.is_running():
raise qubes.exc.QubesVMNotHaltedError( raise qubes.exc.QubesVMNotHaltedError(
'Cannot change name of running domain {!r}'.format(oldvalue)) 'Cannot change name of running domain {!r}'.format(oldvalue))
if self.autostart:
subprocess.check_call(['sudo', 'systemctl', '-q', 'disable',
'qubes-vm@{}.service'.format(oldvalue)])
@qubes.events.handler('property-set:name') @qubes.events.handler('property-set:name')
def on_property_set_name(self, event, name, new_name, old_name=None): def on_property_set_name(self, event, name, new_name, old_name=None):
@ -517,6 +528,9 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
self._update_libvirt_domain() self._update_libvirt_domain()
if self.autostart:
self.autostart = self.autostart
@qubes.events.handler('property-pre-set:autostart') @qubes.events.handler('property-pre-set:autostart')
def on_property_pre_set_autostart(self, event, prop, name, value, def on_property_pre_set_autostart(self, event, prop, name, value,
@ -607,71 +621,72 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
self.storage.prepare_for_vm_startup() self.storage.prepare_for_vm_startup()
self._update_libvirt_domain() self._update_libvirt_domain()
if mem_required is None: qmemman_client = self.request_memory(mem_required)
mem_required = int(self.memory) * 1024 * 1024
if qmemman_present:
qmemman_client = qubes.qmemman.client.QMemmanClient()
try:
got_memory = qmemman_client.request_memory(mem_required)
except IOError as e:
raise IOError('Failed to connect to qmemman: {!s}'.format(e))
if not got_memory:
qmemman_client.close()
raise qubes.exc.QubesMemoryError(self)
# Bind pci devices to pciback driver # Bind pci devices to pciback driver
for pci in self.devices['pci']: for pci in self.devices['pci']:
node = self.app.vmm.libvirt_conn.nodeDeviceLookupByName( try:
'pci_0000_' + pci.replace(':', '_').replace('.', '_')) node = self.app.vmm.libvirt_conn.nodeDeviceLookupByName(
'pci_0000_' + pci.replace(':', '_').replace('.', '_'))
except libvirt.libvirtError as e:
if e.get_error_code() == libvirt.VIR_ERR_NO_NODE_DEVICE:
raise qubes.exc.QubesException(
'PCI device {!r} does not exist (domain {!r})'.format(
pci, self.name))
try: try:
node.dettach() node.dettach()
except libvirt.libvirtError: except libvirt.libvirtError as e:
if self.app.vmm.libvirt_conn.virConnGetLastError()[0] == \ if e.get_error_code() == libvirt.VIR_ERR_INTERNAL_ERROR:
libvirt.VIR_ERR_INTERNAL_ERROR: # allreaddy dettached
# already detached
pass pass
else: else:
raise raise
self.libvirt_domain.createWithFlags(libvirt.VIR_DOMAIN_START_PAUSED) self.libvirt_domain.createWithFlags(libvirt.VIR_DOMAIN_START_PAUSED)
if preparing_dvm: try:
self.services['qubes-dvm'] = True if preparing_dvm:
self.services['qubes-dvm'] = True
self.log.info('Setting Qubes DB info for the VM') self.log.info('Setting Qubes DB info for the VM')
self.start_qubesdb() self.start_qubesdb()
self.create_qdb_entries() self.create_qdb_entries()
self.log.info('Updating firewall rules') self.log.info('Updating firewall rules')
for vm in self.app.domains: for vm in self.app.domains:
if vm.is_proxyvm() and vm.is_running(): if vm.is_proxyvm() and vm.is_running():
vm.write_iptables_xenstore_entry() vm.write_iptables_xenstore_entry()
self.log.warning('Activating the {} VM'.format(self.name)) self.log.warning('Activating the {} VM'.format(self.name))
self.libvirt_domain.resume() self.libvirt_domain.resume()
# close() is not really needed, because the descriptor is close-on-exec # close() is not really needed, because the descriptor is close-on-exec
# anyway, the reason to postpone close() is that possibly xl is not done # anyway, the reason to postpone close() is that possibly xl is not done
# constructing the domain after its main process exits # constructing the domain after its main process exits
# so we close() when we know the domain is up # so we close() when we know the domain is up
# the successful unpause is some indicator of it # the successful unpause is some indicator of it
if qmemman_present: if qmemman_client:
qmemman_client.close() qmemman_client.close()
# if self._start_guid_first and start_guid and not preparing_dvm \ # if self._start_guid_first and start_guid and not preparing_dvm \
# and os.path.exists('/var/run/shm.id'): # and os.path.exists('/var/run/shm.id'):
# self.start_guid() # self.start_guid()
if not preparing_dvm: if not preparing_dvm:
self.start_qrexec_daemon() self.start_qrexec_daemon()
if start_guid and not preparing_dvm \ if start_guid and not preparing_dvm \
and os.path.exists('/var/run/shm.id'): and os.path.exists('/var/run/shm.id'):
self.start_guid() self.start_guid()
self.fire_event('domain-started', self.fire_event('domain-started',
preparing_dvm=preparing_dvm, start_guid=start_guid) preparing_dvm=preparing_dvm, start_guid=start_guid)
except: # pylint: disable=bare-except
self.force_shutdown()
raise
def shutdown(self, force=False): def shutdown(self, force=False):
@ -685,6 +700,19 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
raise qubes.exc.QubesVMNotStartedError(self) raise qubes.exc.QubesVMNotStartedError(self)
self.fire_event_pre('pre-domain-shutdown', force=force) self.fire_event_pre('pre-domain-shutdown', force=force)
# try to gracefully detach PCI devices before shutdown, to mitigate
# timeouts on forcible detach at domain destroy; if that fails, too bad
for pci in self.devices['pci']:
try:
self.libvirt_domain.detachDevice(self.lvxml_pci_dev(pci))
except libvirt.libvirtError as e:
self.log.warning(
'error while gracefully detaching PCI device ({!r}) during'
' shutdown of {!r}; error code: {!r}; continuing'
' anyway'.format(pci, self.name, e.get_error_code()),
exc_info=1)
self.libvirt_domain.shutdown() self.libvirt_domain.shutdown()
@ -850,7 +878,8 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
def run_service(self, service, source=None, user=None, def run_service(self, service, source=None, user=None,
passio_popen=False, input=None): passio_popen=False, input=None, localcmd=None, gui=False,
wait=True):
'''Run service on this VM '''Run service on this VM
**passio_popen** and **input** are mutually exclusive. **passio_popen** and **input** are mutually exclusive.
@ -862,21 +891,51 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
:param str input: string passed as input to service :param str input: string passed as input to service
''' # pylint: disable=redefined-builtin ''' # pylint: disable=redefined-builtin
if input is not None and passio_popen is not None: if len(i for i in (input, passio_popen, localcmd) if i) > 1:
raise ValueError("'input' and 'passio_popen' cannot be used " raise ValueError(
"together") 'input, passio_popen and localcmd cannot be used together')
if input:
localcmd = 'printf %s {}'.format(pipes.quote(input))
source = 'dom0' if source is None else self.app.domains[source].name source = 'dom0' if source is None else self.app.domains[source].name
# XXX TODO FIXME this looks bad... return self.run('QUBESRPC {} {}'.format(service, source),
if input: localcmd=localcmd, passio_popen=passio_popen, user=user, wait=wait,
return self.run("QUBESRPC %s %s" % (service, source), gui=gui)
localcmd="echo %s" % input, user=user, wait=True)
else:
return self.run("QUBESRPC %s %s" % (service, source),
passio_popen=passio_popen, user=user, wait=True)
def request_memory(self, mem_required=None):
# overhead of per-qube/per-vcpu Xen structures,
# taken from OpenStack nova/virt/xenapi/driver.py
# see https://wiki.openstack.org/wiki/XenServer/Overhead
# add an extra MB because Nova rounds up to MBs
if not qmemman_present:
return
MEM_OVERHEAD_BASE = (3 + 1) * 1024 * 1024
MEM_OVERHEAD_PER_CPU = 3 * 1024 * 1024 / 2
if mem_required is None:
mem_required = int(self.memory) * 1024 * 1024
qmemman_client = qubes.qmemman.client.QMemmanClient()
try:
mem_required_with_overhead = mem_required + MEM_OVERHEAD_BASE \
+ self.vcpus * MEM_OVERHEAD_PER_VCPU
got_memory = qmemman_client.request_memory(
mem_required_with_overhead)
except IOError as e:
raise IOError('Failed to connect to qmemman: {!s}'.format(e))
if not got_memory:
qmemman_client.close()
raise qubes.exc.QubesMemoryError(self)
return qmemman_client
def start_guid(self, extra_guid_args=None): def start_guid(self, extra_guid_args=None):
'''Launch gui daemon. '''Launch gui daemon.
@ -900,22 +959,15 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
guid_cmd += ['-v', '-v'] guid_cmd += ['-v', '-v']
# elif not verbose: # elif not verbose:
guid_cmd += ['-q'] else:
guid_cmd += ['-q']
retcode = subprocess.call(guid_cmd) retcode = subprocess.call(guid_cmd)
if retcode != 0: if retcode != 0:
raise qubes.exc.QubesVMError(self, raise qubes.exc.QubesVMError(self,
'Cannot start qubes-guid for domain {!r}'.format(self.name)) 'Cannot start qubes-guid for domain {!r}'.format(self.name))
self.log.info('Sending monitor layout') self.notify_monitor_layout()
try:
subprocess.call(
[qubes.config.system_path['monitor_layout_notify_cmd'],
self.name])
except Exception as e: # pylint: disable=broad-except
self.log.error('error starting gui-daemon: {!s}'.format(e))
self.wait_for_session() self.wait_for_session()
@ -946,13 +998,13 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
self.log.info('Starting Qubes DB') self.log.info('Starting Qubes DB')
# FIXME #1694 #1241
retcode = subprocess.call([ retcode = subprocess.call([
qubes.config.system_path["qubesdb_daemon_path"], qubes.config.system_path["qubesdb_daemon_path"],
str(self.xid), str(self.xid),
self.name]) self.name])
if retcode != 0: if retcode != 0:
self.force_shutdown() raise qubes.exc.QubesException('Cannot execute qubesdb-daemon')
raise OSError("ERROR: Cannot execute qubesdb-daemon!")
def wait_for_session(self): def wait_for_session(self):
@ -970,6 +1022,19 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
p.communicate(input=self.default_user) p.communicate(input=self.default_user)
# TODO event, extension
def notify_monitor_layout(self):
monitor_layout = qubes.monitor_layout.get_monitor_layout()
# notify qube only if we've got a non-empty monitor_layout or else we
# break proper qube resolution set by gui-agent
if not monitor_layout:
return
self.log.info('Sending monitor layout')
qubes.monitor_layout.notify_vm(self, monitor_layout)
# TODO move to storage # TODO move to storage
def create_on_disk(self, source_template=None): def create_on_disk(self, source_template=None):
'''Create files needed for VM. '''Create files needed for VM.
@ -1007,6 +1072,7 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
self.fire_event('domain-created-on-disk', source_template) self.fire_event('domain-created-on-disk', source_template)
# TODO move to storage
def resize_private_img(self, size): def resize_private_img(self, size):
'''Resize private image.''' '''Resize private image.'''
@ -1031,6 +1097,47 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
raise qubes.exc.QubesException('resize2fs failed') raise qubes.exc.QubesException('resize2fs failed')
# TODO move to storage
def resize_root_img(self, size, allow_start=False):
if hasattr(self, 'template'):
raise qubes.exc.QubesVMError(self,
'Cannot resize root.img of template based qube. Resize the'
' root.img of the template instead.')
# TODO self.is_halted
if self.is_running():
raise qubes.exc.QubesVMNotHaltedError(self,
'Cannot resize root.img of a running qube')
if size < self.get_root_img_sz():
raise qubes.exc.QubesValueError(
'For your own safety, shrinking of root.img is disabled. If you'
' really know what you are doing, use `truncate` manually.')
with open(self.root_img, 'a+b') as fd:
fd.truncate(size)
if False: #self.hvm:
return
if not allow_start:
raise qubes.exc.QubesException(
'The qube has to be started to complete the operation, but is'
' required not to start. Either run the operation again allowing'
' starting of the qube this time, or run resize2fs in the qube'
' manually.')
self.start(start_guid=False)
# TODO run_service #1695
self.run('resize2fs /dev/mapper/dmroot', user='root',
wait=True, gui=False)
self.shutdown()
while self.is_running(): #1696
time.sleep(1)
def remove_from_disk(self): def remove_from_disk(self):
'''Remove domain remnants from disk.''' '''Remove domain remnants from disk.'''
self.fire_event('domain-removed-from-disk') self.fire_event('domain-removed-from-disk')
@ -1180,28 +1287,34 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
libvirt_domain = self.libvirt_domain libvirt_domain = self.libvirt_domain
if libvirt_domain is None: if libvirt_domain is None:
return "NA"
if libvirt_domain.isActive():
if libvirt_domain.state()[0] == libvirt.VIR_DOMAIN_PAUSED:
return "Paused"
elif libvirt_domain.state()[0] == libvirt.VIR_DOMAIN_CRASHED:
return "Crashed"
elif libvirt_domain.state()[0] == libvirt.VIR_DOMAIN_SHUTDOWN:
return "Halting"
elif libvirt_domain.state()[0] == libvirt.VIR_DOMAIN_SHUTOFF:
return "Dying"
elif libvirt_domain.state()[0] == libvirt.VIR_DOMAIN_PMSUSPENDED:
return "Suspended"
else:
if not self.is_fully_usable():
return "Transient"
else:
return "Running"
else:
return 'Halted' return 'Halted'
return "NA" try:
if libvirt_domain.isActive():
if libvirt_domain.state()[0] == libvirt.VIR_DOMAIN_PAUSED:
return "Paused"
elif libvirt_domain.state()[0] == libvirt.VIR_DOMAIN_CRASHED:
return "Crashed"
elif libvirt_domain.state()[0] == libvirt.VIR_DOMAIN_SHUTDOWN:
return "Halting"
elif libvirt_domain.state()[0] == libvirt.VIR_DOMAIN_SHUTOFF:
return "Dying"
elif libvirt_domain.state()[0] == libvirt.VIR_DOMAIN_PMSUSPENDED:
return "Suspended"
else:
if not self.is_fully_usable():
return "Transient"
else:
return "Running"
else:
return 'Halted'
except libvirt.libvirtError as e:
if e.get_error_code() == libvirt.VIR_ERR_NO_DOMAIN:
return 'Halted'
else:
raise
assert False
def is_running(self): def is_running(self):
@ -1212,6 +1325,7 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
:rtype: bool :rtype: bool
''' '''
# TODO context manager #1693
return self.libvirt_domain and self.libvirt_domain.isActive() return self.libvirt_domain and self.libvirt_domain.isActive()
@ -1283,10 +1397,26 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
if self.libvirt_domain is None: if self.libvirt_domain is None:
return 0 return 0
if not self.libvirt_domain.isActive():
return 0
return self.libvirt_domain.info()[1] try:
if not self.libvirt_domain.isActive():
return 0
return self.libvirt_domain.info()[1]
except libvirt.libvirtError as e:
if e.get_error_code() in (
# qube no longer exists
libvirt.VIR_ERR_NO_DOMAIN,
# libxl_domain_info failed (race condition from isActive)
libvirt.VIR_ERR_INTERNAL_ERROR,
):
return 0
else:
self.log.exception(
'libvirt error code: {!r}'.format(e.get_error_code()))
raise
def get_mem_static_max(self): def get_mem_static_max(self):
@ -1299,23 +1429,65 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
if self.libvirt_domain is None: if self.libvirt_domain is None:
return 0 return 0
return self.libvirt_domain.maxMemory() try:
return self.libvirt_domain.maxMemory()
except libvirt.libvirtError as e:
if e.get_error_code() in (
# qube no longer exists
libvirt.VIR_ERR_NO_DOMAIN,
# libxl_domain_info failed (race condition from isActive)
libvirt.VIR_ERR_INTERNAL_ERROR,
):
return 0
else:
self.log.exception(
'libvirt error code: {!r}'.format(e.get_error_code()))
raise
def get_per_cpu_time(self): def get_cputime(self):
'''Get total CPU time burned by this domain since start. '''Get total CPU time burned by this domain since start.
:returns: CPU time usage [FIXME unit]. :returns: CPU time usage [FIXME unit].
:rtype: FIXME :rtype: FIXME
''' '''
if self.libvirt_domain is None:
return 0
if self.libvirt_domain is None: if self.libvirt_domain is None:
return 0 return 0
if not self.libvirt_domain.isActive(): if not self.libvirt_domain.isActive():
return 0 return 0
return self.libvirt_domain.getCPUStats( try:
libvirt.VIR_NODE_CPU_STATS_ALL_CPUS, 0)[0]['cpu_time']/10**9 if not self.libvirt_domain.isActive():
return 0
# this does not work, because libvirt
# return self.libvirt_domain.getCPUStats(
# libvirt.VIR_NODE_CPU_STATS_ALL_CPUS, 0)[0]['cpu_time']/10**9
return self.libvirt_domain.info()[4]
except libvirt.libvirtError as e:
if e.get_error_code() in (
# qube no longer exists
libvirt.VIR_ERR_NO_DOMAIN,
# libxl_domain_info failed (race condition from isActive)
libvirt.VIR_ERR_INTERNAL_ERROR,
):
return 0
else:
self.log.exception(
'libvirt error code: {!r}'.format(e.get_error_code()))
raise
# XXX shouldn't this go only to vms that have root image? # XXX shouldn't this go only to vms that have root image?
@ -1502,7 +1674,15 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
self.qdb.write('/name', self.name) self.qdb.write('/name', self.name)
self.qdb.write('/type', self.__class__.__name__) self.qdb.write('/type', self.__class__.__name__)
self.qdb.write('/updateable', str(self.updateable)) self.qdb.write('/updateable', str(self.updateable))
self.qdb.write('/persistence', 'full' if self.updateable else 'rw-only')
self.qdb.write('/debug', str(int(self.debug))) self.qdb.write('/debug', str(int(self.debug)))
try:
self.qdb.write('/template', self.template.name)
except AttributeError:
self.qdb.write('/template', '')
self.qdb.write('/random-seed',
base64.b64encode(qubes.utils.urandom(64)))
if self.provides_network: if self.provides_network:
self.qdb.write('/network-provider/gateway', self.gateway) self.qdb.write('/network-provider/gateway', self.gateway)
@ -1544,17 +1724,15 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
def _update_libvirt_domain(self): def _update_libvirt_domain(self):
'''Re-initialise :py:attr:`libvirt_domain`.''' '''Re-initialise :py:attr:`libvirt_domain`.'''
domain_config = self.create_config_file() domain_config = self.create_config_file()
if self._libvirt_domain is not None:
self._libvirt_domain.undefine()
try: try:
self._libvirt_domain = self.app.vmm.libvirt_conn.defineXML( self._libvirt_domain = self.app.vmm.libvirt_conn.defineXML(
domain_config) domain_config)
except libvirt.libvirtError: except libvirt.libvirtError as e:
if self.app.vmm.libvirt_conn.virConnGetLastError()[0] == \ if e.get_error_code() == libvirt.VIR_ERR_OS_TYPE \
libvirt.VIR_ERR_NO_DOMAIN: and e.get_str2() == 'hvm':
# accept the fact that libvirt doesn't know anything about this raise qubes.exc.QubesVMError(self,
# domain... 'HVM qubes are not supported on this machine. '
pass 'Check BIOS settings for VT-x/AMD-V extensions.')
else: else:
raise raise

View File

@ -28,6 +28,8 @@ from optparse import OptionParser
import subprocess import subprocess
import os import os
import sys import sys
from qubes.qubes import vmm
def handle_vm(vms, label, new_value = None): def handle_vm(vms, label, new_value = None):
functions = { # label: [ getter, setter ], functions = { # label: [ getter, setter ],
@ -116,6 +118,8 @@ def main():
else: else:
options.do_set = True options.do_set = True
vmm.offline_mode = True
if options.do_set: if options.do_set:
qvm_collection = QubesVmCollection() qvm_collection = QubesVmCollection()
qvm_collection.lock_db_for_writing() qvm_collection.lock_db_for_writing()

View File

@ -20,27 +20,37 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
# #
# #
from optparse import OptionParser
import optparse
import os import os
import sys import sys
from qubes.qubes import QubesVmCollection from qubes.qubes import QubesVmCollection
from qubes.qubesutils import updates_vms_toggle,updates_dom0_toggle,\ from qubes.qubesutils import updates_vms_toggle,updates_dom0_toggle,\
updates_dom0_status updates_dom0_status,updates_vms_status
from qubes.qubes import vmm
def usage():
print "Usage: qubes-set-updates enable|disable|status"
print " Enable or disable globally checking for updates (both dom0 and VM)"
print " Status option checks only dom0 updates status"
def main(): def main():
if len(sys.argv) < 2:
usage() usage = "%prog enable|disable|status\n"\
return 1 " Enable or disable globally checking for updates (both dom0 and VM)"
parser = OptionParser (usage)
action = sys.argv[1] parser.add_option("--offline-mode", dest="offline_mode",
action="store_true", default=False,
help=optparse.SUPPRESS_HELP)
(options, args) = parser.parse_args()
if len(args) < 1:
parser.error("You must provide an action")
action = args[0]
if action not in ['enable', 'disable', 'status']: if action not in ['enable', 'disable', 'status']:
usage() parser.error("Invalid action")
return 1
if options.offline_mode:
vmm.offline_mode = True
qvm_collection = QubesVmCollection() qvm_collection = QubesVmCollection()
if action == 'status': if action == 'status':
@ -56,9 +66,16 @@ def main():
updates_vms_toggle(qvm_collection, False) updates_vms_toggle(qvm_collection, False)
else: else:
if updates_dom0_status(qvm_collection): if updates_dom0_status(qvm_collection):
print "enabled" print "dom0: enabled"
else: else:
print "disabled" print "dom0: disabled"
status_vms = updates_vms_status(qvm_collection)
if status_vms is None:
print "vms: mixed"
elif status_vms:
print "vms: enabled"
else:
print "vms: disabled"
if action != 'status': if action != 'status':
qvm_collection.save() qvm_collection.save()

View File

@ -63,6 +63,10 @@ def main():
"list-message-digest-algorithms'") "list-message-digest-algorithms'")
parser.add_option ("-z", "--compress", action="store_true", dest="compress", default=False, parser.add_option ("-z", "--compress", action="store_true", dest="compress", default=False,
help="Compress the backup") help="Compress the backup")
parser.add_option ("-Z", "--compress-filter", action="store",
dest="compress_filter", default=False,
help="Compress the backup using specified filter "
"program (default: gzip)")
parser.add_option ("--debug", action="store_true", dest="debug", parser.add_option ("--debug", action="store_true", dest="debug",
default=False, help="Enable (a lot of) debug output") default=False, help="Enable (a lot of) debug output")
@ -181,7 +185,7 @@ def main():
backup_do(base_backup_dir, files_to_backup, passphrase, backup_do(base_backup_dir, files_to_backup, passphrase,
progress_callback=print_progress, progress_callback=print_progress,
encrypted=options.encrypt, encrypted=options.encrypt,
compressed=options.compress, compressed=options.compress_filter or options.compress,
appvm=appvm, **kwargs) appvm=appvm, **kwargs)
except QubesException as e: except QubesException as e:
print >>sys.stderr, "ERROR: %s" % str(e) print >>sys.stderr, "ERROR: %s" % str(e)

View File

@ -54,6 +54,10 @@ def main():
parser.add_option ("--skip-conflicting", action="store_true", dest="skip_conflicting", default=False, parser.add_option ("--skip-conflicting", action="store_true", dest="skip_conflicting", default=False,
help="Do not restore VMs that are already present on the host") help="Do not restore VMs that are already present on the host")
parser.add_option ("--rename-conflicting", action="store_true",
dest="rename_conflicting", default=False,
help="Restore VMs that are already present on the host under different name")
parser.add_option ("--force-root", action="store_true", dest="force_root", default=False, parser.add_option ("--force-root", action="store_true", dest="force_root", default=False,
help="Force to run, even with root privileges") help="Force to run, even with root privileges")
@ -193,6 +197,9 @@ def main():
else: else:
print >> sys.stderr, "Remove VMs with conflicting names from the host before proceeding." print >> sys.stderr, "Remove VMs with conflicting names from the host before proceeding."
print >> sys.stderr, "... or use --skip-conflicting to restore only those VMs that do not exist on the host." print >> sys.stderr, "... or use --skip-conflicting to restore only those VMs that do not exist on the host."
print >> sys.stderr, "... or use --rename-conflicting to " \
"restore those VMs under modified " \
"name (with number at the end)"
exit (1) exit (1)
print "The above VMs will be copied and added to your system." print "The above VMs will be copied and added to your system."

View File

@ -17,37 +17,43 @@
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
# # USA.
#
import os
import sys
from optparse import OptionParser
from qubes.qubes import QubesVmCollection from qubes.qubes import QubesVmCollection
from qubes.qubes import QubesAppVm, QubesTemplateVm, QubesHVm
from qubes.qubes import QubesException
from optparse import OptionParser;
import sys
import os
def main(): def main():
usage = "usage: %prog [options] <src-name> <new-name>\n"\ usage = "usage: %prog [options] <src-name> <new-name>\n"\
"Clones an existing VM by copying all its disk files" "Clones an existing VM by copying all its disk files"
parser = OptionParser (usage)
parser.add_option ("-q", "--quiet", action="store_false", dest="verbose", default=True)
parser.add_option ("-p", "--path", dest="dir_path",
help="Specify path to the template directory")
parser.add_option ("--force-root", action="store_true", dest="force_root", default=False,
help="Force to run, even with root privileges")
(options, args) = parser.parse_args () parser = OptionParser(usage)
if (len (args) != 2): parser.add_option("-q", "--quiet", action="store_false", dest="verbose",
parser.error ("You must specify at least the src and dst TemplateVM names!") default=True)
parser.add_option("-p", "--path", dest="dir_path",
help="Specify path to the template directory")
parser.add_option("--force-root", action="store_true", dest="force_root",
default=False,
help="Force to run, even with root privileges")
parser.add_option("-P", "--pool", dest="pool_name",
help="Specify in to which storage pool to clone")
(options, args) = parser.parse_args()
if (len(args) != 2):
parser.error(
"You must specify at least the src and dst TemplateVM names!")
srcname = args[0] srcname = args[0]
dstname = args[1] dstname = args[1]
if hasattr(os, "geteuid") and os.geteuid() == 0: if hasattr(os, "geteuid") and os.geteuid() == 0:
if not options.force_root: if not options.force_root:
print >> sys.stderr, "*** Running this tool as root is strongly discouraged, this will lead you in permissions problems." print >> sys.stderr, "*** Running this tool as root is" + \
" strongly discouraged, this will lead you in permissions" + \
"problems."
print >> sys.stderr, "Retry as unprivileged user." print >> sys.stderr, "Retry as unprivileged user."
print >> sys.stderr, "... or use --force-root to continue anyway." print >> sys.stderr, "... or use --force-root to continue anyway."
exit(1) exit(1)
@ -57,12 +63,21 @@ def main():
qvm_collection.load() qvm_collection.load()
src_vm = qvm_collection.get_vm_by_name(srcname) src_vm = qvm_collection.get_vm_by_name(srcname)
if src_vm is None: if src_vm is None:
print >> sys.stderr, "ERROR: A VM with the name '{0}' does not exist in the system.".format(srcname) print >> sys.stderr, \
"ERROR: A VM with the name '{0}' does not exist in the system." \
.format(srcname)
exit(1) exit(1)
if options.pool_name is None:
pool_name = src_vm.pool_name
else:
pool_name = options.pool_name
if qvm_collection.get_vm_by_name(dstname) is not None: if qvm_collection.get_vm_by_name(dstname) is not None:
print >> sys.stderr, "ERROR: A VM with the name '{0}' already exists in the system.".format(dstname) print >> sys.stderr, \
"ERROR: A VM with the name '{0}' already exists in the system." \
.format(dstname)
exit(1) exit(1)
if src_vm.is_disposablevm(): if src_vm.is_disposablevm():
@ -70,19 +85,21 @@ def main():
exit(1) exit(1)
dst_vm = qvm_collection.add_new_vm(src_vm.__class__.__name__, dst_vm = qvm_collection.add_new_vm(src_vm.__class__.__name__,
name=dstname, template=src_vm.template, name=dstname, template=src_vm.template,
dir_path=options.dir_path, installed_by_rpm=False) pool_name=pool_name,
dir_path=options.dir_path,
installed_by_rpm=False)
try: try:
dst_vm.clone_attrs(src_vm) dst_vm.clone_attrs(src_vm)
dst_vm.clone_disk_files (src_vm=src_vm, verbose=options.verbose) dst_vm.clone_disk_files(src_vm=src_vm, verbose=options.verbose)
except (IOError, OSError) as err: except (IOError, OSError) as err:
print >> sys.stderr, "ERROR: {0}".format(err) print >> sys.stderr, "ERROR: {0}".format(err)
qvm_collection.pop(dst_vm.qid) qvm_collection.pop(dst_vm.qid)
dst_vm.remove_from_disk() dst_vm.remove_from_disk()
exit (1) exit(1)
qvm_collection.save() qvm_collection.save()
qvm_collection.unlock_db() qvm_collection.unlock_db()
main() main()

View File

@ -46,16 +46,12 @@ if ! /usr/lib/qubes/qubes-prepare-saved-domain.sh \
exit 1 exit 1
fi fi
DEFAULT=/var/lib/qubes/dvmdata/default-savefile DEFAULT=/var/lib/qubes/dvmdata/default-savefile
DEFAULTCONF=/var/lib/qubes/dvmdata/default-dvm.conf
CURRENT=/var/run/qubes/current-savefile CURRENT=/var/run/qubes/current-savefile
CURRENTCONF=/var/run/qubes/current-dvm.conf
SHMDIR=/dev/shm/qubes SHMDIR=/dev/shm/qubes
SHMCOPY=$SHMDIR/current-savefile SHMCOPY=$SHMDIR/current-savefile
rm -f $ROOT $DEFAULT $CURRENT $DEFAULTCONF $CURRENTCONF rm -f $ROOT $DEFAULT $CURRENT
ln -s "/var/lib/qubes/appvms/$DVMTMPL/dvm-savefile" $DEFAULT ln -s "/var/lib/qubes/appvms/$DVMTMPL/dvm-savefile" $DEFAULT
ln -s "/var/lib/qubes/vm-templates/$TEMPLATENAME/root.img" $ROOT ln -s "/var/lib/qubes/vm-templates/$TEMPLATENAME/root.img" $ROOT
ln -s $DVMTMPLDIR/dvm.conf $DEFAULTCONF
ln -s $DVMTMPLDIR/dvm.conf $CURRENTCONF
if [ -f /var/lib/qubes/dvmdata/dont-use-shm ] ; then if [ -f /var/lib/qubes/dvmdata/dont-use-shm ] ; then
ln -s $DEFAULT $CURRENT ln -s $DEFAULT $CURRENT
else else

View File

@ -34,6 +34,10 @@ def main():
usage = "usage: %prog <vm-name> <size>" usage = "usage: %prog <vm-name> <size>"
parser = OptionParser (usage) parser = OptionParser (usage)
parser.add_option("--allow-start", action="store_true",
dest="allow_start", default=False,
help="Allow VM to be started to complete the operation")
(options, args) = parser.parse_args () (options, args) = parser.parse_args ()
if (len (args) != 2): if (len (args) != 2):
parser.error ("You must specify VM name and new size!") parser.error ("You must specify VM name and new size!")
@ -57,7 +61,7 @@ def main():
exit(1) exit(1)
try: try:
vm.resize_root_img(size_bytes) vm.resize_root_img(size_bytes, allow_start=options.allow_start)
except (IOError, OSError, QubesException) as err: except (IOError, OSError, QubesException) as err:
print >> sys.stderr, "ERROR: {0}".format(err) print >> sys.stderr, "ERROR: {0}".format(err)
exit (1) exit (1)

View File

@ -26,6 +26,27 @@ from optparse import OptionParser
import subprocess import subprocess
import os import os
import sys import sys
from qubes.qubes import vmm
import re
def find_devices_of_class(klass):
p = subprocess.Popen(["/sbin/lspci", "-mm", "-n"], stdout=subprocess.PIPE)
result = p.communicate()
retcode = p.returncode
if retcode != 0:
print "ERROR when executing lspci!"
raise IOError
rx_netdev = re.compile(r"^([0-9a-f]{2}:[0-9a-f]{2}.[0-9a-f]) \"" +
klass)
for dev in str(result[0]).splitlines():
match = rx_netdev.match(dev)
if match is not None:
dev_bdf = match.group(1)
assert dev_bdf is not None
yield dev_bdf
def main(): def main():
usage = "usage: %prog -l [options] <vm-name>\n"\ usage = "usage: %prog -l [options] <vm-name>\n"\
@ -37,6 +58,12 @@ def main():
parser.add_option ("-l", "--list", action="store_true", dest="do_list", default=False) parser.add_option ("-l", "--list", action="store_true", dest="do_list", default=False)
parser.add_option ("-a", "--add", action="store_true", dest="do_add", default=False) parser.add_option ("-a", "--add", action="store_true", dest="do_add", default=False)
parser.add_option ("-d", "--delete", action="store_true", dest="do_delete", default=False) parser.add_option ("-d", "--delete", action="store_true", dest="do_delete", default=False)
parser.add_option("-C", "--add-class", action="store_true",
dest="do_add_class", default=False,
help="Add all devices of given class (net, usb)")
parser.add_option ("--offline-mode", dest="offline_mode",
action="store_true", default=False,
help="Offline mode")
(options, args) = parser.parse_args () (options, args) = parser.parse_args ()
if (len (args) < 1): if (len (args) < 1):
@ -44,11 +71,15 @@ def main():
vmname = args[0] vmname = args[0]
if options.do_list + options.do_add + options.do_delete > 1: if options.do_list + options.do_add + options.do_delete + \
print >> sys.stderr, "Only one of -l -a -d is allowed!" options.do_add_class > 1:
exit (1) print >> sys.stderr, "Only one of -l -a -d -C is allowed!"
exit(1)
if options.do_add or options.do_delete: if options.offline_mode:
vmm.offline_mode = True
if options.do_add or options.do_delete or options.do_add_class:
qvm_collection = QubesVmCollection() qvm_collection = QubesVmCollection()
qvm_collection.lock_db_for_writing() qvm_collection.lock_db_for_writing()
qvm_collection.load() qvm_collection.load()
@ -73,6 +104,26 @@ def main():
qvm_collection.save() qvm_collection.save()
qvm_collection.unlock_db() qvm_collection.unlock_db()
elif options.do_add_class:
if len(args) < 2:
print >> sys.stderr, "You must specify the PCI device class to add"
exit(1)
klass = args[1]
if klass == 'net':
devs = find_devices_of_class("02")
elif klass == 'usb':
devs = find_devices_of_class("0c03")
else:
print >> sys.stderr, "Supported classes: net, usb"
exit(1)
for dev in devs:
vm.pci_add(dev)
qvm_collection.save()
qvm_collection.unlock_db()
elif options.do_delete: elif options.do_delete:
if len (args) < 2: if len (args) < 2:
print >> sys.stderr, "You must specify the PCI device to delete" print >> sys.stderr, "You must specify the PCI device to delete"

View File

@ -75,7 +75,7 @@ def main():
if vm.installed_by_rpm and not options.remove_from_db_only: if vm.installed_by_rpm and not options.remove_from_db_only:
if options.verbose: if options.verbose:
print >> sys.stderr, "This VM has been installed by RPM, use rpm -e <pkg name> to remove it!" print >> sys.stderr, "This VM has been installed by RPM, use yum remove <pkg name> to remove it!"
exit (1) exit (1)
try: try:

View File

@ -36,6 +36,10 @@ def main():
help="Force operation, even if may damage other VMs (eg shutdown of NetVM)") help="Force operation, even if may damage other VMs (eg shutdown of NetVM)")
parser.add_option ("--wait", action="store_true", dest="wait_for_shutdown", default=False, parser.add_option ("--wait", action="store_true", dest="wait_for_shutdown", default=False,
help="Wait for the VM(s) to shutdown") help="Wait for the VM(s) to shutdown")
parser.add_option("--wait-time", action="store", dest="wait_time",
default=defaults["shutdown_counter_max"],
help="Timout after which VM will be killed when --wait "
"is used")
parser.add_option ("--all", action="store_true", dest="shutdown_all", default=False, parser.add_option ("--all", action="store_true", dest="shutdown_all", default=False,
help="Shutdown all running VMs") help="Shutdown all running VMs")
parser.add_option ("--exclude", action="append", dest="exclude_list", parser.add_option ("--exclude", action="append", dest="exclude_list",
@ -107,7 +111,7 @@ def main():
continue continue
else: else:
halting_vms.append(vm) halting_vms.append(vm)
if shutdown_counter > defaults["shutdown_counter_max"]: if shutdown_counter > int(options.wait_time):
# kill the VM # kill the VM
if options.verbose: if options.verbose:
print >> sys.stderr, "Killing the (apparently hanging) VM '{0}'...".format(vm.name) print >> sys.stderr, "Killing the (apparently hanging) VM '{0}'...".format(vm.name)

View File

@ -102,8 +102,10 @@ def main():
if verbose: if verbose:
print >> sys.stderr, '--> Syncing dom0 clock.' print >> sys.stderr, '--> Syncing dom0 clock.'
subprocess.check_call(['sudo', 'date', '-u', '-Iseconds', '-s', date_out]) subprocess.check_call(['sudo', 'date', '-u', '-Iseconds', '-s', date_out],
subprocess.check_call(['sudo', 'hwclock', '--systohc']) stdout=None if verbose else open(os.devnull, 'w'))
subprocess.check_call(['sudo', 'hwclock', '--systohc'],
stdout=None if verbose else open(os.devnull, 'w'))
# Sync other VMs clock # Sync other VMs clock
for vm in qvm_collection.values(): for vm in qvm_collection.values():
@ -111,8 +113,8 @@ def main():
if verbose: if verbose:
print >> sys.stderr, '--> Syncing \'%s\' clock.' % vm.name print >> sys.stderr, '--> Syncing \'%s\' clock.' % vm.name
try: try:
vm.run('date -u -R -s "%s"' % date_out, user="root", vm.run_service("qubes.SetDateTime", user="root",
gui=False, verbose=verbose) localcmd="date -u -Iseconds")
except Exception as e: except Exception as e:
print >> sys.stderr, "ERROR syncing time in VM '%s': %s" % (vm.name, str(e)) print >> sys.stderr, "ERROR syncing time in VM '%s': %s" % (vm.name, str(e))
pass pass

View File

@ -32,7 +32,7 @@ def main():
parser = OptionParser (usage) parser = OptionParser (usage)
parser.add_option ("--offline-mode", dest="offline_mode", parser.add_option ("--offline-mode", dest="offline_mode",
action="store_true", default=False, action="store_true", default=False,
help="Offline mode (ignored in this version)") help="Offline mode")
(options, args) = parser.parse_args () (options, args) = parser.parse_args ()
if (len (args) != 1): if (len (args) != 1):

View File

@ -118,7 +118,12 @@ def main():
''') ''')
fstrim_process.stdin.close() fstrim_process.stdin.close()
qubesutils.block_attach(fstrim_vm, qvm_collection[0], tvm.root_img, qubesutils.block_attach(qvm_collection, fstrim_vm,
{
'vm': 'dom0',
'device': tvm.root_img,
'mode': 'w',
},
mode='w', mode='w',
frontend='xvdi') frontend='xvdi')

View File

@ -70,7 +70,7 @@ Requires: libvirt-python
%if x%{?backend_vmm} == xxen %if x%{?backend_vmm} == xxen
Requires: xen-runtime Requires: xen-runtime
Requires: xen-hvm Requires: xen-hvm
Requires: libvirt-daemon-xen >= 1.2.12-3 Requires: libvirt-daemon-xen >= 1.2.20-4
%endif %endif
Requires: createrepo Requires: createrepo
Requires: gnome-packagekit Requires: gnome-packagekit
@ -171,7 +171,6 @@ if ! grep -q ^qubes: /etc/group ; then
fi fi
%triggerin -- xen-runtime %triggerin -- xen-runtime
sed -i 's/\/block /\/block.qubes /' /etc/udev/rules.d/xen-backend.rules
/usr/lib/qubes/fix-dir-perms.sh /usr/lib/qubes/fix-dir-perms.sh
%preun %preun
@ -187,12 +186,12 @@ if [ "$1" = 0 ] ; then
chgrp root /etc/xen chgrp root /etc/xen
chmod 700 /etc/xen chmod 700 /etc/xen
groupdel qubes groupdel qubes
sed -i 's/\/block.qubes /\/block /' /etc/udev/rules.d/xen-backend.rules
fi fi
%files %files
%defattr(-,root,root,-) %defattr(-,root,root,-)
%config(noreplace) %attr(0664,root,qubes) %{_sysconfdir}/qubes/qmemman.conf %config(noreplace) %attr(0664,root,qubes) %{_sysconfdir}/qubes/qmemman.conf
%config(noreplace) %attr(0664,root,qubes) %{_sysconfdir}/qubes/storage.conf
/usr/bin/qvm-* /usr/bin/qvm-*
/usr/bin/qubes-* /usr/bin/qubes-*
/usr/bin/qmemmand /usr/bin/qmemmand

View File

@ -27,3 +27,7 @@ endif
cp regressions.py[co] $(DESTDIR)$(PYTHON_TESTSPATH) cp regressions.py[co] $(DESTDIR)$(PYTHON_TESTSPATH)
cp run.py $(DESTDIR)$(PYTHON_TESTSPATH) cp run.py $(DESTDIR)$(PYTHON_TESTSPATH)
cp run.py[co] $(DESTDIR)$(PYTHON_TESTSPATH) cp run.py[co] $(DESTDIR)$(PYTHON_TESTSPATH)
cp storage.py $(DESTDIR)$(PYTHON_TESTSPATH)
cp storage.py[co] $(DESTDIR)$(PYTHON_TESTSPATH)
cp storage_xen.py $(DESTDIR)$(PYTHON_TESTSPATH)
cp storage_xen.py[co] $(DESTDIR)$(PYTHON_TESTSPATH)

View File

@ -1,3 +1,412 @@
#!/usr/bin/python2 -O
# vim: fileencoding=utf-8
#
# The Qubes OS Project, https://www.qubes-os.org/
#
# Copyright (C) 2014-2015
# Marek Marczykowski-Górecki <marmarek@invisiblethingslab.com>
# Copyright (C) 2015 Wojtek Porczyk <woju@invisiblethingslab.com>
#
# 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.
#
import multiprocessing
import logging
import os
import shutil
import subprocess
import unittest
import lxml.etree
import sys
import qubes.backup
import qubes.qubes
import time
VMPREFIX = 'test-'
#: :py:obj:`True` if running in dom0, :py:obj:`False` otherwise
in_dom0 = False
#: :py:obj:`False` if outside of git repo,
#: path to root of the directory otherwise
in_git = False
try:
import libvirt
libvirt.openReadOnly(qubes.qubes.defaults['libvirt_uri']).close()
in_dom0 = True
except libvirt.libvirtError:
pass
try:
in_git = subprocess.check_output(
['git', 'rev-parse', '--show-toplevel'],
stderr=open(os.devnull, 'w')).strip()
except subprocess.CalledProcessError:
# git returned nonzero, we are outside git repo
pass
except OSError:
# command not found; let's assume we're outside
pass
def skipUnlessDom0(test_item):
'''Decorator that skips test outside dom0.
Some tests (especially integration tests) have to be run in more or less
working dom0. This is checked by connecting to libvirt.
''' # pylint: disable=invalid-name
return unittest.skipUnless(in_dom0, 'outside dom0')(test_item)
def skipUnlessGit(test_item):
'''Decorator that skips test outside git repo.
There are very few tests that an be run only in git. One example is
correctness of example code that won't get included in RPM.
''' # pylint: disable=invalid-name
return unittest.skipUnless(in_git, 'outside git tree')(test_item)
class _AssertNotRaisesContext(object):
"""A context manager used to implement TestCase.assertNotRaises methods.
Stolen from unittest and hacked. Regexp support stripped.
"""
def __init__(self, expected, test_case, expected_regexp=None):
self.expected = expected
self.failureException = test_case.failureException
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, tb):
if exc_type is None:
return True
try:
exc_name = self.expected.__name__
except AttributeError:
exc_name = str(self.expected)
if issubclass(exc_type, self.expected):
raise self.failureException(
"{0} raised".format(exc_name))
else:
# pass through
return False
self.exception = exc_value # store for later retrieval
class BeforeCleanExit(BaseException):
pass
class QubesTestCase(unittest.TestCase):
'''Base class for Qubes unit tests.
'''
def __init__(self, *args, **kwargs):
super(QubesTestCase, self).__init__(*args, **kwargs)
self.longMessage = True
self.log = logging.getLogger('{}.{}.{}'.format(
self.__class__.__module__,
self.__class__.__name__,
self._testMethodName))
def __str__(self):
return '{}/{}/{}'.format(
'.'.join(self.__class__.__module__.split('.')[2:]),
self.__class__.__name__,
self._testMethodName)
def tearDown(self):
super(QubesTestCase, self).tearDown()
result = self._resultForDoCleanups
l = result.failures \
+ result.errors \
+ [(tc, None) for tc in result.unexpectedSuccesses]
if getattr(result, 'do_not_clean', False) \
and filter((lambda (tc, exc): tc is self), l):
raise BeforeCleanExit()
def assertNotRaises(self, excClass, callableObj=None, *args, **kwargs):
"""Fail if an exception of class excClass is raised
by callableObj when invoked with arguments args and keyword
arguments kwargs. If a different type of exception is
raised, it will not be caught, and the test case will be
deemed to have suffered an error, exactly as for an
unexpected exception.
If called with callableObj omitted or None, will return a
context object used like this::
with self.assertRaises(SomeException):
do_something()
The context manager keeps a reference to the exception as
the 'exception' attribute. This allows you to inspect the
exception after the assertion::
with self.assertRaises(SomeException) as cm:
do_something()
the_exception = cm.exception
self.assertEqual(the_exception.error_code, 3)
"""
context = _AssertNotRaisesContext(excClass, self)
if callableObj is None:
return context
with context:
callableObj(*args, **kwargs)
def assertXMLEqual(self, xml1, xml2):
"""Check for equality of two XML objects.
:param xml1: first element
:param xml2: second element
:type xml1: :py:class:`lxml.etree._Element`
:type xml2: :py:class:`lxml.etree._Element`
""" # pylint: disable=invalid-name
self.assertEqual(xml1.tag, xml2.tag)
self.assertEqual(xml1.text, xml2.text)
self.assertItemsEqual(xml1.keys(), xml2.keys())
for key in xml1.keys():
self.assertEqual(xml1.get(key), xml2.get(key))
class SystemTestsMixin(object):
def setUp(self):
"""Set up the test.
.. warning::
This method instantiates QubesVmCollection acquires write lock for
it. You can use is as :py:attr:`qc`. You can (and probably
should) release the lock at the end of setUp in subclass
"""
super(SystemTestsMixin, self).setUp()
self.qc = qubes.qubes.QubesVmCollection()
self.qc.lock_db_for_writing()
self.qc.load()
self.conn = libvirt.open(qubes.qubes.defaults['libvirt_uri'])
self.remove_test_vms()
def tearDown(self):
super(SystemTestsMixin, self).tearDown()
# release the lock, because we have no way to check whether it was
# read or write lock
try:
self.qc.unlock_db()
except qubes.qubes.QubesException:
pass
self.kill_test_vms()
self.qc.lock_db_for_writing()
self.qc.load()
self.remove_test_vms()
self.qc.save()
self.qc.unlock_db()
del self.qc
self.conn.close()
def make_vm_name(self, name):
return VMPREFIX + name
def save_and_reload_db(self):
self.qc.save()
self.qc.unlock_db()
self.qc.lock_db_for_writing()
self.qc.load()
def kill_test_vms(self):
# do not keep write lock while killing VMs, because that may cause a
# deadlock with disk hotplug scripts (namely qvm-template-commit
# called when shutting down TemplateVm)
self.qc.lock_db_for_reading()
self.qc.load()
self.qc.unlock_db()
for vm in self.qc.values():
if vm.name.startswith(VMPREFIX):
if vm.is_running():
vm.force_shutdown()
def _remove_vm_qubes(self, vm):
vmname = vm.name
try:
# XXX .is_running() may throw libvirtError if undefined
if vm.is_running():
vm.force_shutdown()
except:
pass
try:
vm.remove_from_disk()
except:
pass
try:
vm.libvirt_domain.undefine()
except libvirt.libvirtError:
pass
self.qc.pop(vm.qid)
del vm
# Now ensure it really went away. This may not have happened,
# for example if vm.libvirtDomain malfunctioned.
try:
dom = self.conn.lookupByName(vmname)
except:
pass
else:
self._remove_vm_libvirt(dom)
self._remove_vm_disk(vmname)
def _remove_vm_libvirt(self, dom):
try:
dom.destroy()
except libvirt.libvirtError: # not running
pass
dom.undefine()
def _remove_vm_disk(self, vmname):
for dirspec in (
'qubes_appvms_dir',
'qubes_servicevms_dir',
'qubes_templates_dir'):
dirpath = os.path.join(qubes.qubes.system_path['qubes_base_dir'],
qubes.qubes.system_path[dirspec], vmname)
if os.path.exists(dirpath):
if os.path.isdir(dirpath):
shutil.rmtree(dirpath)
else:
os.unlink(dirpath)
def remove_vms(self, vms):
for vm in vms: self._remove_vm_qubes(vm)
self.save_and_reload_db()
def remove_test_vms(self):
"""Aggresively remove any domain that has name in testing namespace.
.. warning::
The test suite hereby claims any domain whose name starts with
:py:data:`VMPREFIX` as fair game. This is needed to enforce sane
test executing environment. If you have domains named ``test-*``,
don't run the tests.
"""
# first, remove them Qubes-way
something_removed = False
for vm in self.qc.values():
if vm.name.startswith(VMPREFIX):
self._remove_vm_qubes(vm)
something_removed = True
if something_removed:
self.save_and_reload_db()
# now remove what was only in libvirt
for dom in self.conn.listAllDomains():
if dom.name().startswith(VMPREFIX):
self._remove_vm_libvirt(dom)
# finally remove anything that is left on disk
vmnames = set()
for dirspec in (
'qubes_appvms_dir',
'qubes_servicevms_dir',
'qubes_templates_dir'):
dirpath = os.path.join(qubes.qubes.system_path['qubes_base_dir'],
qubes.qubes.system_path[dirspec])
for name in os.listdir(dirpath):
if name.startswith(VMPREFIX):
vmnames.add(name)
for vmname in vmnames:
self._remove_vm_disk(vmname)
def wait_for_window(self, title, timeout=30, show=True):
"""
Wait for a window with a given title. Depending on show parameter,
it will wait for either window to show or to disappear.
:param title: title of the window to wait for
:param timeout: timeout of the operation, in seconds
:param show: if True - wait for the window to be visible,
otherwise - to not be visible
:return: None
"""
wait_count = 0
while subprocess.call(['xdotool', 'search', '--name', title],
stdout=open(os.path.devnull, 'w'),
stderr=subprocess.STDOUT) == int(show):
wait_count += 1
if wait_count > timeout*10:
self.fail("Timeout while waiting for {} window to {}".format(
title, "show" if show else "hide")
)
time.sleep(0.1)
def enter_keys_in_window(self, title, keys):
"""
Search for window with given title, then enter listed keys there.
The function will wait for said window to appear.
:param title: title of window
:param keys: list of keys to enter, as for `xdotool key`
:return: None
"""
# 'xdotool search --sync' sometimes crashes on some race when
# accessing window properties
self.wait_for_window(title)
command = ['xdotool', 'search', '--name', title,
'windowactivate',
'key'] + keys
subprocess.check_call(command)
def shutdown_and_wait(self, vm, timeout=60):
vm.shutdown()
while timeout > 0:
if not vm.is_running():
return
time.sleep(1)
timeout -= 1
self.fail("Timeout while waiting for VM {} shutdown".format(vm.name))
class BackupTestsMixin(SystemTestsMixin): class BackupTestsMixin(SystemTestsMixin):
def setUp(self): def setUp(self):
super(BackupTestsMixin, self).setUp() super(BackupTestsMixin, self).setUp()
@ -7,39 +416,28 @@ class BackupTestsMixin(SystemTestsMixin):
if self.verbose: if self.verbose:
print >>sys.stderr, "-> Creating backupvm" print >>sys.stderr, "-> Creating backupvm"
# TODO: allow non-default template
self.backupvm = self.qc.add_new_vm("QubesAppVm",
name=self.make_vm_name('backupvm'),
template=self.qc.get_default_template())
self.backupvm.create_on_disk(verbose=self.verbose)
self.backupdir = os.path.join(os.environ["HOME"], "test-backup") self.backupdir = os.path.join(os.environ["HOME"], "test-backup")
if os.path.exists(self.backupdir): if os.path.exists(self.backupdir):
shutil.rmtree(self.backupdir) shutil.rmtree(self.backupdir)
os.mkdir(self.backupdir) os.mkdir(self.backupdir)
def tearDown(self): def tearDown(self):
super(BackupTestsMixin, self).tearDown() super(BackupTestsMixin, self).tearDown()
shutil.rmtree(self.backupdir) shutil.rmtree(self.backupdir)
def print_progress(self, progress): def print_progress(self, progress):
if self.verbose: if self.verbose:
print >> sys.stderr, "\r-> Backing up files: {0}%...".format(progress) print >> sys.stderr, "\r-> Backing up files: {0}%...".format(progress)
def error_callback(self, message): def error_callback(self, message):
self.error_detected.put(message) self.error_detected.put(message)
if self.verbose: if self.verbose:
print >> sys.stderr, "ERROR: {0}".format(message) print >> sys.stderr, "ERROR: {0}".format(message)
def print_callback(self, msg): def print_callback(self, msg):
if self.verbose: if self.verbose:
print msg print msg
def fill_image(self, path, size=None, sparse=False): def fill_image(self, path, size=None, sparse=False):
block_size = 4096 block_size = 4096
@ -58,17 +456,27 @@ class BackupTestsMixin(SystemTestsMixin):
f.close() f.close()
# NOTE: this was create_basic_vms # NOTE: this was create_basic_vms
def create_backup_vms(self): def create_backup_vms(self):
template=self.qc.get_default_template() template=self.qc.get_default_template()
vms = [] vms = []
vmname = self.make_vm_name('test-net')
if self.verbose:
print >>sys.stderr, "-> Creating %s" % vmname
testnet = self.qc.add_new_vm('QubesNetVm',
name=vmname, template=template)
testnet.create_on_disk(verbose=self.verbose)
vms.append(testnet)
self.fill_image(testnet.private_img, 20*1024*1024)
vmname = self.make_vm_name('test1') vmname = self.make_vm_name('test1')
if self.verbose: if self.verbose:
print >>sys.stderr, "-> Creating %s" % vmname print >>sys.stderr, "-> Creating %s" % vmname
testvm1 = self.qc.add_new_vm('QubesAppVm', testvm1 = self.qc.add_new_vm('QubesAppVm',
name=vmname, template=template) name=vmname, template=template)
testvm1.uses_default_netvm = False
testvm1.netvm = testnet
testvm1.create_on_disk(verbose=self.verbose) testvm1.create_on_disk(verbose=self.verbose)
vms.append(testvm1) vms.append(testvm1)
self.fill_image(testvm1.private_img, 100*1024*1024) self.fill_image(testvm1.private_img, 100*1024*1024)
@ -85,9 +493,8 @@ class BackupTestsMixin(SystemTestsMixin):
return vms return vms
def make_backup(self, vms, prepare_kwargs=dict(), do_kwargs=dict(), def make_backup(self, vms, prepare_kwargs=dict(), do_kwargs=dict(),
target=None): target=None, expect_failure=False):
# XXX: bakup_prepare and backup_do don't support host_collection # XXX: bakup_prepare and backup_do don't support host_collection
self.qc.unlock_db() self.qc.unlock_db()
if target is None: if target is None:
@ -98,20 +505,26 @@ class BackupTestsMixin(SystemTestsMixin):
print_callback=self.print_callback, print_callback=self.print_callback,
**prepare_kwargs) **prepare_kwargs)
except qubes.qubes.QubesException as e: except qubes.qubes.QubesException as e:
self.fail("QubesException during backup_prepare: %s" % str(e)) if not expect_failure:
self.fail("QubesException during backup_prepare: %s" % str(e))
else:
raise
try: try:
qubes.backup.backup_do(target, files_to_backup, "qubes", qubes.backup.backup_do(target, files_to_backup, "qubes",
progress_callback=self.print_progress, progress_callback=self.print_progress,
**do_kwargs) **do_kwargs)
except qubes.qubes.QubesException as e: except qubes.qubes.QubesException as e:
self.fail("QubesException during backup_do: %s" % str(e)) if not expect_failure:
self.fail("QubesException during backup_do: %s" % str(e))
else:
raise
self.qc.lock_db_for_writing() self.qc.lock_db_for_writing()
self.qc.load() self.qc.load()
def restore_backup(self, source=None, appvm=None, options=None,
def restore_backup(self, source=None, appvm=None, options=None): expect_errors=None):
if source is None: if source is None:
backupfile = os.path.join(self.backupdir, backupfile = os.path.join(self.backupdir,
sorted(os.listdir(self.backupdir))[-1]) sorted(os.listdir(self.backupdir))[-1])
@ -140,16 +553,43 @@ class BackupTestsMixin(SystemTestsMixin):
self.qc.load() self.qc.load()
errors = [] errors = []
if expect_errors is None:
expect_errors = []
while not self.error_detected.empty(): while not self.error_detected.empty():
errors.append(self.error_detected.get()) current_error = self.error_detected.get()
if any(map(current_error.startswith, expect_errors)):
continue
errors.append(current_error)
self.assertTrue(len(errors) == 0, self.assertTrue(len(errors) == 0,
"Error(s) detected during backup_restore_do: %s" % "Error(s) detected during backup_restore_do: %s" %
'\n'.join(errors)) '\n'.join(errors))
if not appvm and not os.path.isdir(backupfile): if not appvm and not os.path.isdir(backupfile):
os.unlink(backupfile) os.unlink(backupfile)
def create_sparse(self, path, size): def create_sparse(self, path, size):
f = open(path, "w") f = open(path, "w")
f.truncate(size) f.truncate(size)
f.close() f.close()
def load_tests(loader, tests, pattern):
# discard any tests from this module, because it hosts base classes
tests = unittest.TestSuite()
for modname in (
'qubes.tests.basic',
'qubes.tests.dom0_update',
'qubes.tests.network',
'qubes.tests.vm_qrexec_gui',
'qubes.tests.backup',
'qubes.tests.backupcompatibility',
'qubes.tests.regressions',
'qubes.tests.storage',
'qubes.tests.storage_xen',
):
tests.addTests(loader.loadTestsFromName(modname))
return tests
# vim: ts=4 sw=4 et

View File

@ -28,7 +28,7 @@ import os
import unittest import unittest
import sys import sys
from qubes.qubes import QubesException, QubesTemplateVm
import qubes.tests import qubes.tests
class TC_00_Backup(qubes.tests.BackupTestsMixin, qubes.tests.QubesTestCase): class TC_00_Backup(qubes.tests.BackupTestsMixin, qubes.tests.QubesTestCase):
@ -63,7 +63,6 @@ class TC_00_Backup(qubes.tests.BackupTestsMixin, qubes.tests.QubesTestCase):
self.restore_backup() self.restore_backup()
self.remove_vms(vms) self.remove_vms(vms)
def test_004_sparse_multipart(self): def test_004_sparse_multipart(self):
vms = [] vms = []
@ -85,9 +84,84 @@ class TC_00_Backup(qubes.tests.BackupTestsMixin, qubes.tests.QubesTestCase):
self.restore_backup() self.restore_backup()
self.remove_vms(vms) self.remove_vms(vms)
def test_005_compressed_custom(self):
vms = self.create_backup_vms()
self.make_backup(vms, do_kwargs={'compressed': "bzip2"})
self.remove_vms(vms)
self.restore_backup()
self.remove_vms(vms)
# TODO: iterate over templates def test_100_backup_dom0_no_restore(self):
def test_100_send_to_vm(self): self.make_backup([self.qc[0]])
# TODO: think of some safe way to test restore...
def test_200_restore_over_existing_directory(self):
"""
Regression test for #1386
:return:
"""
vms = self.create_backup_vms()
self.make_backup(vms)
self.remove_vms(vms)
test_dir = vms[0].dir_path
os.mkdir(test_dir)
with open(os.path.join(test_dir, 'some-file.txt'), 'w') as f:
f.write('test file\n')
self.restore_backup(
expect_errors=[
'*** Directory {} already exists! It has been moved'.format(
test_dir)
])
self.remove_vms(vms)
def test_210_auto_rename(self):
"""
Test for #869
:return:
"""
vms = self.create_backup_vms()
self.make_backup(vms)
self.restore_backup(options={
'rename-conflicting': True
})
for vm in vms:
self.assertIsNotNone(self.qc.get_vm_by_name(vm.name+'1'))
restored_vm = self.qc.get_vm_by_name(vm.name+'1')
if vm.netvm and not vm.uses_default_netvm:
self.assertEqual(restored_vm.netvm.name, vm.netvm.name+'1')
self.remove_vms(vms)
class TC_10_BackupVMMixin(qubes.tests.BackupTestsMixin):
def setUp(self):
super(TC_10_BackupVMMixin, self).setUp()
self.backupvm = self.qc.add_new_vm(
"QubesAppVm",
name=self.make_vm_name('backupvm'),
template=self.qc.get_vm_by_name(self.template)
)
self.backupvm.create_on_disk(verbose=self.verbose)
def test_100_send_to_vm_file_with_spaces(self):
vms = self.create_backup_vms()
self.backupvm.start()
self.backupvm.run("mkdir '/var/tmp/backup directory'", wait=True)
self.make_backup(vms,
do_kwargs={
'appvm': self.backupvm,
'compressed': True,
'encrypted': True},
target='/var/tmp/backup directory')
self.remove_vms(vms)
p = self.backupvm.run("ls /var/tmp/backup*/qubes-backup*",
passio_popen=True)
(backup_path, _) = p.communicate()
backup_path = backup_path.strip()
self.restore_backup(source=backup_path,
appvm=self.backupvm)
self.remove_vms(vms)
def test_110_send_to_vm_command(self):
vms = self.create_backup_vms() vms = self.create_backup_vms()
self.backupvm.start() self.backupvm.start()
self.make_backup(vms, self.make_backup(vms,
@ -100,3 +174,52 @@ class TC_00_Backup(qubes.tests.BackupTestsMixin, qubes.tests.QubesTestCase):
self.restore_backup(source='dd if=/var/tmp/backup-test', self.restore_backup(source='dd if=/var/tmp/backup-test',
appvm=self.backupvm) appvm=self.backupvm)
self.remove_vms(vms) self.remove_vms(vms)
def test_110_send_to_vm_no_space(self):
"""
Check whether backup properly report failure when no enough space is
available
:return:
"""
vms = self.create_backup_vms()
self.backupvm.start()
retcode = self.backupvm.run(
"truncate -s 50M /home/user/backup.img && "
"mkfs.ext4 -F /home/user/backup.img && "
"mkdir /home/user/backup && "
"mount /home/user/backup.img /home/user/backup -o loop &&"
"chmod 777 /home/user/backup",
user="root", wait=True)
if retcode != 0:
raise RuntimeError("Failed to prepare backup directory")
with self.assertRaises(QubesException):
self.make_backup(vms,
do_kwargs={
'appvm': self.backupvm,
'compressed': False,
'encrypted': True},
target='/home/user/backup',
expect_failure=True)
self.qc.lock_db_for_writing()
self.qc.load()
self.remove_vms(vms)
def load_tests(loader, tests, pattern):
try:
qc = qubes.qubes.QubesVmCollection()
qc.lock_db_for_reading()
qc.load()
qc.unlock_db()
templates = [vm.name for vm in qc.values() if
isinstance(vm, QubesTemplateVm)]
except OSError:
templates = []
for template in templates:
tests.addTests(loader.loadTestsFromTestCase(
type(
'TC_10_BackupVM_' + template,
(TC_10_BackupVMMixin, qubes.tests.QubesTestCase),
{'template': template})))
return tests

File diff suppressed because one or more lines are too long

View File

@ -34,8 +34,10 @@ VM_PREFIX = "test-"
@unittest.skipUnless(os.path.exists('/usr/bin/rpmsign') and @unittest.skipUnless(os.path.exists('/usr/bin/rpmsign') and
os.path.exists('/usr/bin/rpmbuild'), os.path.exists('/usr/bin/rpmbuild'),
'rpm-sign and/or rpm-build not installed') 'rpm-sign and/or rpm-build not installed')
class TC_00_Dom0Upgrade(qubes.tests.QubesTestCase): class TC_00_Dom0UpgradeMixin(qubes.tests.SystemTestsMixin):
cleanup_paths = [] """
Tests for downloading dom0 updates using VMs based on different templates
"""
pkg_name = 'qubes-test-pkg' pkg_name = 'qubes-test-pkg'
dom0_update_common_opts = ['--disablerepo=*', '--enablerepo=test', dom0_update_common_opts = ['--disablerepo=*', '--enablerepo=test',
'--setopt=test.copy_local=1'] '--setopt=test.copy_local=1']
@ -70,10 +72,9 @@ Expire-Date: 0
@classmethod @classmethod
def setUpClass(cls): def setUpClass(cls):
super(TC_00_Dom0Upgrade, cls).setUpClass() super(TC_00_Dom0UpgradeMixin, cls).setUpClass()
cls.tmpdir = tempfile.mkdtemp() cls.tmpdir = tempfile.mkdtemp()
cls.cleanup_paths += [cls.tmpdir]
cls.keyid = cls.generate_key(cls.tmpdir) cls.keyid = cls.generate_key(cls.tmpdir)
@ -95,17 +96,14 @@ enabled = 1
subprocess.check_call(['sudo', 'rm', '-f', subprocess.check_call(['sudo', 'rm', '-f',
'/etc/yum.repos.d/test.repo']) '/etc/yum.repos.d/test.repo'])
for dir in cls.cleanup_paths: shutil.rmtree(cls.tmpdir)
shutil.rmtree(dir)
cls.cleanup_paths = []
def setUp(self): def setUp(self):
self.qc = QubesVmCollection() super(TC_00_Dom0UpgradeMixin, self).setUp()
self.qc.lock_db_for_writing() self.updatevm = self.qc.add_new_vm(
self.qc.load() "QubesProxyVm",
self.updatevm = self.qc.add_new_vm("QubesProxyVm", name=self.make_vm_name("updatevm"),
name="%supdatevm" % VM_PREFIX, template=self.qc.get_vm_by_name(self.template))
template=self.qc.get_default_template())
self.updatevm.create_on_disk(verbose=False) self.updatevm.create_on_disk(verbose=False)
self.saved_updatevm = self.qc.get_updatevm_vm() self.saved_updatevm = self.qc.get_updatevm_vm()
self.qc.set_updatevm_vm(self.updatevm) self.qc.set_updatevm_vm(self.updatevm)
@ -117,35 +115,13 @@ enabled = 1
os.path.join(self.tmpdir, 'pubkey.asc')]) os.path.join(self.tmpdir, 'pubkey.asc')])
self.updatevm.start() self.updatevm.start()
def tearDown(self):
def remove_vms(self, vms):
self.qc.lock_db_for_writing() self.qc.lock_db_for_writing()
self.qc.load() self.qc.load()
self.qc.set_updatevm_vm(self.qc[self.saved_updatevm.qid]) self.qc.set_updatevm_vm(self.qc[self.saved_updatevm.qid])
for vm in vms:
if isinstance(vm, str):
vm = self.qc.get_vm_by_name(vm)
else:
vm = self.qc[vm.qid]
if vm.is_running():
try:
vm.force_shutdown()
except:
pass
try:
vm.remove_from_disk()
except OSError:
pass
self.qc.pop(vm.qid)
self.qc.save() self.qc.save()
self.qc.unlock_db() super(TC_00_Dom0UpgradeMixin, self).tearDown()
def tearDown(self):
vmlist = [vm for vm in self.qc.values() if vm.name.startswith(
VM_PREFIX)]
self.remove_vms(vmlist)
subprocess.call(['sudo', 'rpm', '-e', self.pkg_name], stderr=open( subprocess.call(['sudo', 'rpm', '-e', self.pkg_name], stderr=open(
os.devnull, 'w')) os.devnull, 'w'))
@ -202,7 +178,13 @@ Test package
p.stdin.write(open(filename).read()) p.stdin.write(open(filename).read())
p.stdin.close() p.stdin.close()
p.wait() p.wait()
self.updatevm.run('cd /tmp/repo; createrepo .', wait=True) retcode = self.updatevm.run('cd /tmp/repo; createrepo .', wait=True)
if retcode == 127:
self.skipTest("createrepo not installed in template {}".format(
self.template))
elif retcode != 0:
self.skipTest("createrepo failed with code {}, cannot perform the "
"test".format(retcode))
def test_000_update(self): def test_000_update(self):
filename = self.create_pkg(self.tmpdir, self.pkg_name, '1.0') filename = self.create_pkg(self.tmpdir, self.pkg_name, '1.0')
@ -297,3 +279,23 @@ Test package
self.pkg_name)], stdout=open('/dev/null', 'w')) self.pkg_name)], stdout=open('/dev/null', 'w'))
self.assertEqual(retcode, 1, self.assertEqual(retcode, 1,
'UNSIGNED package {}-1.0 installed'.format(self.pkg_name)) 'UNSIGNED package {}-1.0 installed'.format(self.pkg_name))
def load_tests(loader, tests, pattern):
try:
qc = qubes.qubes.QubesVmCollection()
qc.lock_db_for_reading()
qc.load()
qc.unlock_db()
templates = [vm.name for vm in qc.values() if
isinstance(vm, qubes.qubes.QubesTemplateVm)]
except OSError:
templates = []
for template in templates:
tests.addTests(loader.loadTestsFromTestCase(
type(
'TC_00_Dom0Upgrade_' + template,
(TC_00_Dom0UpgradeMixin, qubes.tests.QubesTestCase),
{'template': template})))
return tests

View File

@ -320,12 +320,323 @@ class VmNetworkingMixin(qubes.tests.SystemTestsMixin):
self.testvm1.start() self.testvm1.start()
self.assertEqual(self.run_cmd(self.testvm1, self.ping_ip), 0) self.assertEqual(self.run_cmd(self.testvm1, self.ping_ip), 0)
self.testvm1.run("ip addr flush dev eth0", user="root") self.testvm1.run("ip addr flush dev eth0", user="root", wait=True)
self.testvm1.run("ip addr add 10.137.1.128/24 dev eth0", user="root") self.testvm1.run("ip addr add 10.137.1.128/24 dev eth0", user="root",
self.testvm1.run("ip route add dev eth0", user="root") wait=True)
self.testvm1.run("ip route add default dev eth0", user="root",
wait=True)
self.assertNotEqual(self.run_cmd(self.testvm1, self.ping_ip), 0, self.assertNotEqual(self.run_cmd(self.testvm1, self.ping_ip), 0,
"Spoofed ping should be blocked") "Spoofed ping should be blocked")
class VmUpdatesMixin(qubes.tests.SystemTestsMixin):
"""
Tests for VM updates
"""
# made this way to work also when no package build tools are installed
"""
$ cat test-pkg.spec:
Name: test-pkg
Version: 1.0
Release: 1%{?dist}
Summary: Test package
Group: System
License: GPL
URL: http://example.com/
%description
Test package
%files
%changelog
$ rpmbuild -bb test-pkg.spec
$ cat test-pkg-1.0-1.fc21.x86_64.rpm | gzip | base64
"""
RPM_PACKAGE_GZIP_BASE64 = (
"H4sIAPzRLlYAA+2Y728URRjHn7ueUCkERKJVJDnTxLSxs7293o8WOER6ljYYrtKCLUSa3"
"bnZ64bd22VmTq8nr4wJbwxvjNHIG0x8oTHGGCHB8AcYE1/0lS80GgmQFCJU3wgB4ZjdfZ"
"q2xDe8NNlvMjfzmeeZH7tPbl98b35169cOUEpIJiTxT9SIrmVUs2hWh8dUAp54dOrM14s"
"JHK4D2DKl+j2qrVfjsuq3qEWbohjuAB2Lqk+p1o/8Z5QPmSi/YwnjezH+F8bLQZjqllW0"
"hvODRmFIL5hFk9JMXi/mi5ZuDleNwSEzP5wtmLnouNQnm3/6fndz7FLt9M/Hruj37gav4"
"tTjPnasWLFixYoVK1asWLFixYoV63+p0KNot9vnIPQc1vgYOwCSgXfxCoS+QzKHOVXVOj"
"Fn2ccIfI0k8nXkLuQbyJthxed4UrVnkG8i9yDfgsj3yCAv4foc8t+w1hf5B+Nl5Du43xj"
"yvxivIN9HpsgPkO2IU9uQfeRn8Xk/iJ4x1Y3nfxH1qecwfhH5+YgT25F7o/0SRdxvOppP"
"7MX9ZjB/DNnE/OOYX404uRGZIT+FbCFvQ3aQ8f0+/WF0XjJ8nyOw7H+BrmUA/a8pNZf2D"
"XrCqLG1cERbWHI8ajhznpBY9P0Tr8PkvJDMhTkp/Z0DA6xpuL7DNOq5A+DY9UYTmkOF2U"
"IO/sNt0wSnGvfdlZssD3rVIlLI9UUX37C6qXzHNntHPNfnTAhWHbUddtBwmegDjAUzZbu"
"m9lqZmzDmHc8Ik8WY8Tab4Myym4+Gx8V0qw8GtYyWIzrktEJwV9UHv3ktG471rAqHTmFQ"
"685V5uGqIalk06SWJr7tszR503Ac9cs493jJ8rhrSCIYbXBbzqt5v5+UZ0crh6bGR2dmJ"
"yuHD428VlLLLdakzJe2VxcKhFSFID73JKPS40RI7tXVCcQ3uOGWhPCJ2bAspiJ2i5Vy6n"
"jOqMerpEYpEe/Yks4xkU4Tt6BirmzUWanG6ozbFKhve9BsQRaLRTirzqk7hgUktXojKnf"
"n8jeg3X4QepP3i63po6oml+9t/CwJLya2Bn/ei6f7/4B3Ycdb0L3pt5Q5mNz16rWJ9fLk"
"vvOff/nxS7//8O2P2gvt7nDDnoV9L1du9N4+ucjl9u/8+a7dC5Nnvjlv9Ox5r+v9Cy0NE"
"m+c6rv60S/dZw98Gn6MNswcfQiWUvg3wBUAAA=="
)
"""
Minimal package generated by running dh_make on empty directory
Then cat test-pkg_1.0-1_amd64.deb | gzip | base64
"""
DEB_PACKAGE_GZIP_BASE64 = (
"H4sIACTXLlYAA1O0SSxKzrDjSklNykzM003KzEssqlRQUDA0MTG1NDQwNDVTUDBQAAEIa"
"WhgYGZioqBgogADCVxGegZcyfl5JUX5OXoliUV66VVE6DcwheuX7+ZgAAEW5rdXHb0PG4"
"iwf5j3WfMT6zWzzMuZgoE3jjYraNzbbFKWGms0SaRw/r2SV23WZ4IdP8preM4yqf0jt95"
"3c8qnacfNxJUkf9/w+/3X9ph2GEdgQdixrz/niHKKTnYXizf4oSC7tHOz2Zzq+/6vn8/7"
"ezQ7c1tmi7xZ3SGJ4yzhT2dcr7V+W3zM5ZPu/56PSv4Zdok+7Yv/V/6buWaKVlFkkV58S"
"N3GmLgnqzRmeZ3V3ymmurS5fGa85/LNx1bpZMin3S6dvXKqydp3ubP1vmyarJZb/qSh62"
"C8oIdxqm/BtvkGDza+On/Vfv2py7/0LV7VH+qR6a+bkKUbHXt5/SG187d+nps1a5PJfMO"
"i11dWcUe1HjwaW3Q5RHXn9LmcHy+tW9YcKf0768XVB1t3R0bKrzs5t9P+6r7rZ99svH10"
"+Q6F/o8tf1fO/32y+fWa14eifd+WxUy0jcxYH7N9/tUvmnUZL74pW32qLeuRU+ZwYGASa"
"GBgUWBgxM90ayy3VdmykkGDgYErJbEkERydFVWQmCMQo8aWZvAY/WteFRHFwMCYqXTPjI"
"lBkVEMGLsl+k8XP1D/z+gXyyDOvUemlnHqAVkvu0rRQ2fUFodkN3mtU9uwhqk8V+TqPEE"
"Nc7fzoQ4n71lqRs/7kbbT0+qOZuKH4r8mjzsc1k/YkCHN8Pjg48fbpE+teHa96LNcfu0V"
"5n2/Z2xa2KDvaCOx8cqBFxc514uZ3TmadXS+6cpzU7wSzq5SWfapJOD9n6wLXSwtlgxZh"
"xITzWW7buhx/bb291RcVlEfeC9K5hlrqunSzIMSZT7/Nqgc/qMvMNW227WI8ezB8mVuZh"
"0hERJSvysfburr4Dx0I9BW57UwR4+e1gxu49PcEt8sbK18Xpvt//Hj5UYm+Zc25q+T4xl"
"rJvxfVnh80oadq57OZxPaU1bbztv1yF365W4t45Yr+XrFzov237GVY1Zgf7NvE4+W2SuR"
"lQtLauR1TQ/mbOiIONYya6tU1jPGpWfk/i1+ttiXe3ZO14n0YOWggndznjGlGLyfVbBC6"
"MRP5aMM7aCco/s7sZqB8RlTQwADw8rnuT/sDHi7mUASjJFRAAbWwNLiAwAA"
)
def run_cmd(self, vm, cmd, user="root"):
p = vm.run(cmd, user=user, passio_popen=True, ignore_stderr=True)
p.stdin.close()
p.stdout.read()
return p.wait()
def setUp(self):
super(VmUpdatesMixin, self).setUp()
self.update_cmd = None
if self.template.count("debian"):
self.update_cmd = "set -o pipefail; apt-get update 2>&1 | " \
"{ ! grep '^W:\|^E:'; }"
self.install_cmd = "apt-get install -y {}"
self.install_test_cmd = "dpkg -l {}"
self.exit_code_ok = [0]
elif self.template.count("fedora"):
cmd = "yum"
try:
# assume template name in form "fedora-XX-suffix"
if int(self.template.split("-")[1]) > 21:
cmd = "dnf"
except ValueError:
pass
self.update_cmd = "{cmd} clean all; {cmd} check-update".format(
cmd=cmd)
self.install_cmd = cmd + " install -y {}"
self.install_test_cmd = "rpm -q {}"
self.exit_code_ok = [0, 100]
else:
self.skipTest("Template {} not supported by this test".format(
self.template))
self.testvm1 = self.qc.add_new_vm(
"QubesAppVm",
name=self.make_vm_name('vm1'),
template=self.qc.get_vm_by_name(self.template))
self.testvm1.create_on_disk(verbose=False)
def test_000_simple_update(self):
self.save_and_reload_db()
self.qc.unlock_db()
# reload the VM to have all the properties properly set (especially
# default netvm)
self.testvm1 = self.qc[self.testvm1.qid]
self.testvm1.start()
p = self.testvm1.run(self.update_cmd, wait=True, user="root",
passio_popen=True, passio_stderr=True)
(stdout, stderr) = p.communicate()
self.assertIn(p.wait(), self.exit_code_ok,
"{}: {}\n{}".format(self.update_cmd, stdout, stderr)
)
def create_repo_apt(self):
pkg_file_name = "test-pkg_1.0-1_amd64.deb"
p = self.netvm_repo.run("mkdir /tmp/apt-repo && cd /tmp/apt-repo &&"
"base64 -d | zcat > {}".format(pkg_file_name),
passio_popen=True)
p.stdin.write(self.DEB_PACKAGE_GZIP_BASE64)
p.stdin.close()
if p.wait() != 0:
raise RuntimeError("Failed to write {}".format(pkg_file_name))
# do not assume dpkg-scanpackage installed
packages_path = "dists/test/main/binary-amd64/Packages"
p = self.netvm_repo.run(
"mkdir -p /tmp/apt-repo/dists/test/main/binary-amd64 && "
"cd /tmp/apt-repo && "
"cat > {packages} && "
"echo MD5sum: $(openssl md5 -r {pkg} | cut -f 1 -d ' ')"
" >> {packages} && "
"echo SHA1: $(openssl sha1 -r {pkg} | cut -f 1 -d ' ')"
" >> {packages} && "
"echo SHA256: $(openssl sha256 -r {pkg} | cut -f 1 -d ' ')"
" >> {packages} && "
"gzip < {packages} > {packages}.gz".format(pkg=pkg_file_name,
packages=packages_path),
passio_popen=True, passio_stderr=True)
p.stdin.write(
"Package: test-pkg\n"
"Version: 1.0-1\n"
"Architecture: amd64\n"
"Maintainer: unknown <user@host>\n"
"Installed-Size: 25\n"
"Filename: {pkg}\n"
"Size: 994\n"
"Section: unknown\n"
"Priority: optional\n"
"Description: Test package\n".format(pkg=pkg_file_name)
)
p.stdin.close()
if p.wait() != 0:
raise RuntimeError("Failed to write Packages file: {}".format(
p.stderr.read()))
p = self.netvm_repo.run(
"mkdir -p /tmp/apt-repo/dists/test && "
"cd /tmp/apt-repo/dists/test && "
"cat > Release <<EOF && "
"echo '' $(sha1sum {p} | cut -f 1 -d ' ') $(stat -c %s {p}) {p}"
" >> Release && "
"echo '' $(sha1sum {z} | cut -f 1 -d ' ') $(stat -c %s {z}) {z}"
" >> Release"
.format(p="main/binary-amd64/Packages",
z="main/binary-amd64/Packages.gz"),
passio_popen=True, passio_stderr=True
)
p.stdin.write(
"Label: Test repo\n"
"Suite: test\n"
"Codename: test\n"
"Date: Tue, 27 Oct 2015 03:22:09 +0100\n"
"Architectures: amd64\n"
"Components: main\n"
"SHA1:\n"
"EOF\n"
)
p.stdin.close()
if p.wait() != 0:
raise RuntimeError("Failed to write Release file: {}".format(
p.stderr.read()))
def create_repo_yum(self):
pkg_file_name = "test-pkg-1.0-1.fc21.x86_64.rpm"
p = self.netvm_repo.run("mkdir /tmp/yum-repo && cd /tmp/yum-repo &&"
"base64 -d | zcat > {}".format(pkg_file_name),
passio_popen=True, passio_stderr=True)
p.stdin.write(self.RPM_PACKAGE_GZIP_BASE64)
p.stdin.close()
if p.wait() != 0:
raise RuntimeError("Failed to write {}: {}".format(pkg_file_name,
p.stderr.read()))
# createrepo is installed by default in Fedora template
p = self.netvm_repo.run("createrepo /tmp/yum-repo",
passio_popen=True,
passio_stderr=True)
if p.wait() != 0:
raise RuntimeError("Failed to create yum metadata: {}".format(
p.stderr.read()))
def create_repo_and_serve(self):
if self.template.count("debian") or self.template.count("whonix"):
self.create_repo_apt()
self.netvm_repo.run("cd /tmp/apt-repo &&"
"python -m SimpleHTTPServer 8080")
elif self.template.count("fedora"):
self.create_repo_yum()
self.netvm_repo.run("cd /tmp/yum-repo &&"
"python -m SimpleHTTPServer 8080")
else:
# not reachable...
self.skipTest("Template {} not supported by this test".format(
self.template))
def configure_test_repo(self):
"""
Configure test repository in test-vm and disable rest of them.
The critical part is to use "localhost" - this will work only when
accessed through update proxy and this is exactly what we want to
test here.
"""
if self.template.count("debian") or self.template.count("whonix"):
self.testvm1.run(
"rm -f /etc/apt/sources.list.d/* &&"
"echo 'deb [trusted=yes] http://localhost:8080 test main' "
"> /etc/apt/sources.list",
user="root")
elif self.template.count("fedora"):
self.testvm1.run(
"rm -f /etc/yum.repos.d/*.repo &&"
"echo '[test]' > /etc/yum.repos.d/test.repo &&"
"echo 'name=Test repo' >> /etc/yum.repos.d/test.repo &&"
"echo 'gpgcheck=0' >> /etc/yum.repos.d/test.repo &&"
"echo 'baseurl=http://localhost:8080/'"
" >> /etc/yum.repos.d/test.repo",
user="root"
)
else:
# not reachable...
self.skipTest("Template {} not supported by this test".format(
self.template))
def test_010_update_via_proxy(self):
"""
Test both whether updates proxy works and whether is actually used by the VM
"""
if self.template.count("minimal"):
self.skipTest("Template {} not supported by this test".format(
self.template))
self.netvm_repo = self.qc.add_new_vm(
"QubesNetVm",
name=self.make_vm_name('net'),
template=self.qc.get_vm_by_name(self.template))
self.netvm_repo.create_on_disk(verbose=False)
self.testvm1.netvm = self.netvm_repo
# NetVM should have qubes-updates-proxy enabled by default
#self.netvm_repo.services['qubes-updates-proxy'] = True
# TODO: consider also adding a test for the template itself
self.testvm1.services['updates-proxy-setup'] = True
self.qc.save()
self.qc.unlock_db()
# Setup test repo
self.netvm_repo.start()
self.create_repo_and_serve()
# Configure local repo
self.testvm1.start()
self.configure_test_repo()
# update repository metadata
p = self.testvm1.run(self.update_cmd, wait=True, user="root",
passio_popen=True, passio_stderr=True)
(stdout, stderr) = p.communicate()
self.assertIn(p.wait(), self.exit_code_ok,
"{}: {}\n{}".format(self.update_cmd, stdout, stderr)
)
# install test package
p = self.testvm1.run(self.install_cmd.format('test-pkg'),
wait=True, user="root",
passio_popen=True, passio_stderr=True)
(stdout, stderr) = p.communicate()
self.assertIn(p.wait(), self.exit_code_ok,
"{}: {}\n{}".format(self.update_cmd, stdout, stderr)
)
# verify if it was really installed
p = self.testvm1.run(self.install_test_cmd.format('test-pkg'),
wait=True, user="root",
passio_popen=True, passio_stderr=True)
(stdout, stderr) = p.communicate()
self.assertIn(p.wait(), self.exit_code_ok,
"{}: {}\n{}".format(self.update_cmd, stdout, stderr)
)
def load_tests(loader, tests, pattern): def load_tests(loader, tests, pattern):
try: try:
@ -343,4 +654,9 @@ def load_tests(loader, tests, pattern):
'VmNetworking_' + template, 'VmNetworking_' + template,
(VmNetworkingMixin, qubes.tests.QubesTestCase), (VmNetworkingMixin, qubes.tests.QubesTestCase),
{'template': template}))) {'template': template})))
tests.addTests(loader.loadTestsFromTestCase(
type(
'VmUpdates_' + template,
(VmUpdatesMixin, qubes.tests.QubesTestCase),
{'template': template})))
return tests return tests

View File

@ -1,4 +1,5 @@
#!/usr/bin/python2 -O #!/usr/bin/python2 -O
# coding=utf-8
# #
# The Qubes OS Project, https://www.qubes-os.org/ # The Qubes OS Project, https://www.qubes-os.org/
@ -23,11 +24,14 @@
# #
import multiprocessing import multiprocessing
import os
import time import time
import unittest import unittest
import qubes.qubes import qubes.qubes
import qubes.tests import qubes.tests
import subprocess
class TC_00_Regressions(qubes.tests.SystemTestsMixin, qubes.tests.QubesTestCase): class TC_00_Regressions(qubes.tests.SystemTestsMixin, qubes.tests.QubesTestCase):
# Bug: #906 # Bug: #906
@ -56,3 +60,22 @@ class TC_00_Regressions(qubes.tests.SystemTestsMixin, qubes.tests.QubesTestCase)
self.assertIsNotNone(qc.get_vm_by_name(vmname1)) self.assertIsNotNone(qc.get_vm_by_name(vmname1))
self.assertIsNotNone(qc.get_vm_by_name(vmname2)) self.assertIsNotNone(qc.get_vm_by_name(vmname2))
def test_bug_1389_dispvm_qubesdb_crash(self):
"""
Sometimes QubesDB instance in DispVM crashes at startup.
Unfortunately we don't have reliable way to reproduce it, so try twice
:return:
"""
self.qc.unlock_db()
for try_no in xrange(2):
p = subprocess.Popen(['/usr/lib/qubes/qfile-daemon-dvm',
'qubes.VMShell', 'dom0', 'DEFAULT'],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=open(os.devnull, 'w'))
p.stdin.write("qubesdb-read /name || echo ERROR\n")
dispvm_name = p.stdout.readline()
p.stdin.close()
self.assertTrue(dispvm_name.startswith("disp"),
"Try {} failed".format(try_no))

77
tests/storage.py Normal file
View File

@ -0,0 +1,77 @@
# The Qubes OS Project, https://www.qubes-os.org/
#
# Copyright (C) 2015 Bahtiar `kalkin-` Gadimov <bahtiar@gadimov.de>
#
# 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.
import qubes.storage
from qubes.qubes import defaults
from qubes.storage.xen import XenPool, XenStorage
from qubes.tests import QubesTestCase, SystemTestsMixin
class TC_00_Storage(SystemTestsMixin, QubesTestCase):
""" This class tests the utility methods from :mod:``qubes.storage`` """
def test_000_dump(self):
""" Dumps storage instance to a storage string """
vmname = self.make_vm_name('appvm')
template = self.qc.get_default_template()
vm = self.qc.add_new_vm('QubesAppVm', name=vmname,
pool_name='default', template=template)
storage = vm.storage
result = qubes.storage.dump(storage)
expected = 'qubes.storage.xen.XenStorage'
self.assertEquals(result, expected)
def test_001_load(self):
""" Loads storage driver from a storage string """
result = qubes.storage.load('qubes.storage.xen.XenStorage')
self.assertTrue(result is XenStorage)
def test_002_default_pool_drivers(self):
""" The only predifined pool driver is xen """
result = defaults['pool_drivers'].keys()
expected = ["xen"]
self.assertEquals(result, expected)
def test_003_get_pool_klass(self):
""" Expect the default pool to be `XenPool` """
result = qubes.storage._get_pool_klass('default')
self.assertTrue(result is XenPool)
def test_004_pool_exists_default(self):
""" Expect the default pool to exists """
self.assertTrue(qubes.storage.pool_exists('default'))
def test_005_pool_exists_random(self):
""" Expect this pool to not a exist """
self.assertFalse(
qubes.storage.pool_exists('asdh312096r832598213iudhas'))
def test_006_add_remove_pool(self):
""" Tries to adding and removing a pool. """
pool_name = 'asdjhrp89132'
# make sure it's really does not exist
qubes.storage.remove_pool(pool_name)
qubes.storage.add_pool(pool_name, driver='xen')
self.assertTrue(qubes.storage.pool_exists(pool_name))
qubes.storage.remove_pool(pool_name)
self.assertFalse(qubes.storage.pool_exists(pool_name))

228
tests/storage_xen.py Normal file
View File

@ -0,0 +1,228 @@
# The Qubes OS Project, https://www.qubes-os.org/
#
# Copyright (C) 2015 Bahtiar `kalkin-` Gadimov <bahtiar@gadimov.de>
#
# 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.
import os
import shutil
import qubes.storage
from qubes.tests import QubesTestCase, SystemTestsMixin
from qubes.storage.xen import XenStorage
class TC_00_XenPool(SystemTestsMixin, QubesTestCase):
""" This class tests some properties of the 'default' pool. """
def test000_default_pool_dir(self):
""" The predefined dir for the default pool should be ``/var/lib/qubes``
.. sealso::
Data :data:``qubes.qubes.defaults['pool_config']``.
"""
vm = self._init_app_vm()
result = qubes.storage.get_pool("default", vm).dir_path
expected = '/var/lib/qubes/'
self.assertEquals(result, expected)
def test001_default_storage_class(self):
""" Check when using default pool the Storage is ``XenStorage``. """
result = self._init_app_vm().storage
self.assertIsInstance(result, XenStorage)
def test_002_default_pool_name(self):
""" Default pool_name is 'default'. """
vm = self._init_app_vm()
self.assertEquals(vm.pool_name, "default")
def _init_app_vm(self):
""" Return initalised, but not created, AppVm. """
vmname = self.make_vm_name('appvm')
template = self.qc.get_default_template()
return self.qc.add_new_vm('QubesAppVm', name=vmname, template=template,
pool_name='default')
class TC_01_XenPool(SystemTestsMixin, QubesTestCase):
""" Test the paths for the default Xen file based storage (``XenStorage``).
"""
POOL_DIR = '/var/lib/qubes/test-pool'
APPVMS_DIR = '/var/lib/qubes/test-pool/appvms'
TEMPLATES_DIR = '/var/lib/qubes/test-pool/vm-templates'
SERVICE_DIR = '/var/lib/qubes/test-pool/servicevms'
def setUp(self):
""" Add a test file based storage pool """
super(TC_01_XenPool, self).setUp()
qubes.storage.add_pool('test-pool', driver='xen',
dir_path=self.POOL_DIR)
def tearDown(self):
""" Remove the file based storage pool after testing """
super(TC_01_XenPool, self).tearDown()
qubes.storage.remove_pool("test-pool")
shutil.rmtree(self.POOL_DIR, ignore_errors=True)
def test_001_pool_exists(self):
""" Check if the storage pool was added to the storage pool config """
self.assertTrue(qubes.storage.pool_exists('test-pool'))
def test_002_pool_dir_create(self):
""" Check if the storage pool dir and subdirs were created """
# The dir should not exists before
self.assertFalse(os.path.exists(self.POOL_DIR))
vmname = self.make_vm_name('appvm')
template = self.qc.get_default_template()
self.qc.add_new_vm('QubesAppVm', name=vmname, template=template,
pool_name='test-pool')
self.assertTrue(os.path.exists(self.POOL_DIR))
self.assertTrue(os.path.exists(self.APPVMS_DIR))
self.assertTrue(os.path.exists(self.SERVICE_DIR))
self.assertTrue(os.path.exists(self.TEMPLATES_DIR))
def test_003_pool_dir(self):
""" Check if the vm storage pool_dir is the same as specified """
vmname = self.make_vm_name('appvm')
template = self.qc.get_default_template()
vm = self.qc.add_new_vm('QubesAppVm', name=vmname, template=template,
pool_name='test-pool')
result = qubes.storage.get_pool('test-pool', vm).dir_path
self.assertEquals(self.POOL_DIR, result)
def test_004_app_vmdir(self):
""" Check the vm storage dir for an AppVm"""
vmname = self.make_vm_name('appvm')
template = self.qc.get_default_template()
vm = self.qc.add_new_vm('QubesAppVm', name=vmname, template=template,
pool_name='test-pool')
expected = os.path.join(self.APPVMS_DIR, vm.name)
result = vm.storage.vmdir
self.assertEquals(expected, result)
def test_005_hvm_vmdir(self):
""" Check the vm storage dir for a HVM"""
vmname = self.make_vm_name('hvm')
vm = self.qc.add_new_vm('QubesHVm', name=vmname,
pool_name='test-pool')
expected = os.path.join(self.APPVMS_DIR, vm.name)
result = vm.storage.vmdir
self.assertEquals(expected, result)
def test_006_net_vmdir(self):
""" Check the vm storage dir for a Netvm"""
vmname = self.make_vm_name('hvm')
vm = self.qc.add_new_vm('QubesNetVm', name=vmname,
pool_name='test-pool')
expected = os.path.join(self.SERVICE_DIR, vm.name)
result = vm.storage.vmdir
self.assertEquals(expected, result)
def test_007_proxy_vmdir(self):
""" Check the vm storage dir for a ProxyVm"""
vmname = self.make_vm_name('proxyvm')
vm = self.qc.add_new_vm('QubesProxyVm', name=vmname,
pool_name='test-pool')
expected = os.path.join(self.SERVICE_DIR, vm.name)
result = vm.storage.vmdir
self.assertEquals(expected, result)
def test_008_admin_vmdir(self):
""" Check the vm storage dir for a AdminVm"""
# TODO How to test AdminVm?
pass
def test_009_template_vmdir(self):
""" Check the vm storage dir for a TemplateVm"""
vmname = self.make_vm_name('templatevm')
vm = self.qc.add_new_vm('QubesTemplateVm', name=vmname,
pool_name='test-pool')
expected = os.path.join(self.TEMPLATES_DIR, vm.name)
result = vm.storage.vmdir
self.assertEquals(expected, result)
def test_010_template_hvm_vmdir(self):
""" Check the vm storage dir for a TemplateHVm"""
vmname = self.make_vm_name('templatehvm')
vm = self.qc.add_new_vm('QubesTemplateHVm', name=vmname,
pool_name='test-pool')
expected = os.path.join(self.TEMPLATES_DIR, vm.name)
result = vm.storage.vmdir
self.assertEquals(expected, result)
def test_011_appvm_file_images(self):
""" Check if all the needed image files are created for an AppVm"""
vmname = self.make_vm_name('appvm')
template = self.qc.get_default_template()
vm = self.qc.add_new_vm('QubesAppVm', name=vmname, template=template,
pool_name='test-pool')
vm.create_on_disk(verbose=False)
expected_vmdir = os.path.join(self.APPVMS_DIR, vm.name)
self.assertEqualsAndExists(vm.storage.vmdir, expected_vmdir)
expected_private_path = os.path.join(expected_vmdir, 'private.img')
self.assertEqualsAndExists(vm.storage.private_img,
expected_private_path)
expected_volatile_path = os.path.join(expected_vmdir, 'volatile.img')
self.assertEqualsAndExists(vm.storage.volatile_img,
expected_volatile_path)
def test_012_hvm_file_images(self):
""" Check if all the needed image files are created for a HVm"""
vmname = self.make_vm_name('hvm')
vm = self.qc.add_new_vm('QubesHVm', name=vmname,
pool_name='test-pool')
vm.create_on_disk(verbose=False)
expected_vmdir = os.path.join(self.APPVMS_DIR, vm.name)
self.assertEqualsAndExists(vm.storage.vmdir, expected_vmdir)
expected_private_path = os.path.join(expected_vmdir, 'private.img')
self.assertEqualsAndExists(vm.storage.private_img,
expected_private_path)
expected_root_path = os.path.join(expected_vmdir, 'root.img')
self.assertEqualsAndExists(vm.storage.root_img, expected_root_path)
expected_volatile_path = os.path.join(expected_vmdir, 'volatile.img')
self.assertEqualsAndExists(vm.storage.volatile_img,
expected_volatile_path)
def assertEqualsAndExists(self, result_path, expected_path):
""" Check if the ``result_path``, matches ``expected_path`` and exists.
See also: :meth:``assertExist``
"""
self.assertEquals(result_path, expected_path)
self.assertExist(result_path)
def assertExist(self, path):
""" Assert that the given path exists. """
self.assertTrue(os.path.exists(path))

File diff suppressed because it is too large Load Diff

View File

@ -1 +1 @@
3.0.14 3.1.11

View File

@ -9,8 +9,6 @@
<loader>hvmloader</loader> <loader>hvmloader</loader>
<boot dev='cdrom'/> <boot dev='cdrom'/>
<boot dev='hd'/> <boot dev='hd'/>
<!-- server_ip is the address of stubdomain. It hosts it's own DNS server. -->
{disable_network1}<cmdline>-net lwip,client_ip={ip},server_ip={dns2},dns={dns1},gw={gateway},netmask={netmask}</cmdline>{disable_network2}
</os> </os>
<features> <features>
<pae/> <pae/>
@ -23,14 +21,18 @@
<on_reboot>destroy</on_reboot> <on_reboot>destroy</on_reboot>
<on_crash>destroy</on_crash> <on_crash>destroy</on_crash>
<devices> <devices>
<emulator type='stubdom'/> {no_network_begin}<emulator type='stubdom'/>{no_network_end}
<!-- server_ip is the address of stubdomain. It hosts it's own DNS server. -->
{network_begin}<emulator type='stubdom' cmdline='-net lwip,client_ip={ip},server_ip={dns2},dns={dns1},gw={gateway},netmask={netmask}'/>{network_end}
{rootdev} {rootdev}
{privatedev} {privatedev}
{otherdevs} {otherdevs}
{netdev} {netdev}
{pcidevs} {pcidevs}
<input type='tablet' bus='usb'/> <input type='tablet' bus='usb'/>
<video type='vga'/> <video type='vga'>
<model type='xen' vram='16384'/>
</video>
</devices> </devices>
</domain> </domain>