Remove core2 code
This all either have been migrated to core3, or is not needed anymore. There is still qvm-tools directory with a few tools that needs to be migrated, or installed as is.
This commit is contained in:
parent
bb4dc91ee8
commit
8992e71f85
7
Makefile
7
Makefile
@ -40,16 +40,10 @@ rpms-dom0:
|
|||||||
$(RPMS_DIR)/x86_64/qubes-core-dom0-$(VERSION)*.rpm \
|
$(RPMS_DIR)/x86_64/qubes-core-dom0-$(VERSION)*.rpm \
|
||||||
$(RPMS_DIR)/noarch/qubes-core-dom0-doc-$(VERSION)*rpm
|
$(RPMS_DIR)/noarch/qubes-core-dom0-doc-$(VERSION)*rpm
|
||||||
|
|
||||||
clean:
|
|
||||||
make -C dispvm clean
|
|
||||||
|
|
||||||
all:
|
all:
|
||||||
$(PYTHON) setup.py build
|
$(PYTHON) setup.py build
|
||||||
# make all -C tests
|
# make all -C tests
|
||||||
# Currently supported only on xen
|
# Currently supported only on xen
|
||||||
ifeq ($(BACKEND_VMM),xen)
|
|
||||||
make all -C dispvm
|
|
||||||
endif
|
|
||||||
|
|
||||||
install:
|
install:
|
||||||
ifeq ($(OS),Linux)
|
ifeq ($(OS),Linux)
|
||||||
@ -67,7 +61,6 @@ ifeq ($(BACKEND_VMM),xen)
|
|||||||
# Currently supported only on xen
|
# Currently supported only on xen
|
||||||
cp etc/qmemman.conf $(DESTDIR)/etc/qubes/
|
cp etc/qmemman.conf $(DESTDIR)/etc/qubes/
|
||||||
endif
|
endif
|
||||||
$(MAKE) install -C dispvm
|
|
||||||
mkdir -p $(DESTDIR)/etc/qubes-rpc/policy
|
mkdir -p $(DESTDIR)/etc/qubes-rpc/policy
|
||||||
mkdir -p $(DESTDIR)/usr/libexec/qubes
|
mkdir -p $(DESTDIR)/usr/libexec/qubes
|
||||||
cp qubes-rpc-policy/qubes.FeaturesRequest.policy $(DESTDIR)/etc/qubes-rpc/policy/qubes.FeaturesRequest
|
cp qubes-rpc-policy/qubes.FeaturesRequest.policy $(DESTDIR)/etc/qubes-rpc/policy/qubes.FeaturesRequest
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,77 +0,0 @@
|
|||||||
#!/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)
|
|
@ -1,54 +0,0 @@
|
|||||||
#!/usr/bin/python2
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# The Qubes OS Project, http://www.qubes-os.org
|
|
||||||
#
|
|
||||||
# Copyright (C) 2010 Joanna Rutkowska <joanna@invisiblethingslab.com>
|
|
||||||
# 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.
|
|
||||||
#
|
|
||||||
#
|
|
||||||
|
|
||||||
import os.path
|
|
||||||
|
|
||||||
from qubes.qubes import (
|
|
||||||
register_qubes_vm_class,
|
|
||||||
system_path,
|
|
||||||
QubesResizableVmWithResize2fs,
|
|
||||||
QubesVmLabel,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class QubesAppVm(QubesResizableVmWithResize2fs):
|
|
||||||
"""
|
|
||||||
A class that represents an AppVM. A child of QubesVm.
|
|
||||||
"""
|
|
||||||
def get_attrs_config(self):
|
|
||||||
attrs_config = super(QubesAppVm, self).get_attrs_config()
|
|
||||||
attrs_config['dir_path']['func'] = \
|
|
||||||
lambda value: value if value is not None else \
|
|
||||||
os.path.join(system_path["qubes_appvms_dir"], self.name)
|
|
||||||
|
|
||||||
return attrs_config
|
|
||||||
|
|
||||||
@property
|
|
||||||
def type(self):
|
|
||||||
return "AppVM"
|
|
||||||
|
|
||||||
def is_appvm(self):
|
|
||||||
return True
|
|
||||||
|
|
||||||
register_qubes_vm_class(QubesAppVm)
|
|
@ -1,248 +0,0 @@
|
|||||||
#!/usr/bin/python2
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# The Qubes OS Project, http://www.qubes-os.org
|
|
||||||
#
|
|
||||||
# Copyright (C) 2010 Joanna Rutkowska <joanna@invisiblethingslab.com>
|
|
||||||
# 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.
|
|
||||||
#
|
|
||||||
#
|
|
||||||
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import libvirt
|
|
||||||
import time
|
|
||||||
from qubes.qubes import QubesVm,QubesVmLabel,register_qubes_vm_class, \
|
|
||||||
QubesException
|
|
||||||
from qubes.qubes import QubesDispVmLabels
|
|
||||||
from qubes.qubes import dry_run,vmm
|
|
||||||
import grp
|
|
||||||
|
|
||||||
qmemman_present = False
|
|
||||||
try:
|
|
||||||
from qubes.qmemman_client import QMemmanClient
|
|
||||||
qmemman_present = True
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
DISPID_STATE_FILE = '/var/run/qubes/dispid'
|
|
||||||
GUID_SHMID_FILE = ['/var/run/qubes/shm.id', '/var/run/shm.id']
|
|
||||||
|
|
||||||
class QubesDisposableVm(QubesVm):
|
|
||||||
"""
|
|
||||||
A class that represents an DisposableVM. A child of QubesVm.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# In which order load this VM type from qubes.xml
|
|
||||||
load_order = 120
|
|
||||||
|
|
||||||
|
|
||||||
def _assign_new_dispid(self):
|
|
||||||
# This method in called while lock on qubes.xml is held, so no need for
|
|
||||||
# additional lock
|
|
||||||
if os.path.exists(DISPID_STATE_FILE):
|
|
||||||
f = open(DISPID_STATE_FILE, 'r+')
|
|
||||||
dispid = int(f.read())
|
|
||||||
f.seek(0)
|
|
||||||
f.truncate(0)
|
|
||||||
f.write(str(dispid+1))
|
|
||||||
f.close()
|
|
||||||
else:
|
|
||||||
dispid = 1
|
|
||||||
f = open(DISPID_STATE_FILE, 'w')
|
|
||||||
f.write(str(dispid+1))
|
|
||||||
f.close()
|
|
||||||
os.chown(DISPID_STATE_FILE, -1, grp.getgrnam('qubes').gr_gid)
|
|
||||||
os.chmod(DISPID_STATE_FILE, 0664)
|
|
||||||
return dispid
|
|
||||||
|
|
||||||
def get_attrs_config(self):
|
|
||||||
attrs_config = super(QubesDisposableVm, self).get_attrs_config()
|
|
||||||
|
|
||||||
attrs_config['name']['func'] = \
|
|
||||||
lambda x: "disp%d" % self.dispid if x is None else x
|
|
||||||
|
|
||||||
# New attributes
|
|
||||||
attrs_config['dispid'] = {
|
|
||||||
'func': lambda x: (self._assign_new_dispid() if x is None
|
|
||||||
else int(x)),
|
|
||||||
'save': lambda: str(self.dispid),
|
|
||||||
# needs to be set before name
|
|
||||||
'order': 0
|
|
||||||
}
|
|
||||||
attrs_config['include_in_backups']['func'] = lambda x: False
|
|
||||||
attrs_config['disp_savefile'] = {
|
|
||||||
'default': '/var/run/qubes/current-savefile',
|
|
||||||
'save': lambda: str(self.disp_savefile) }
|
|
||||||
|
|
||||||
return attrs_config
|
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
|
||||||
|
|
||||||
disp_template = None
|
|
||||||
if 'disp_template' in kwargs.keys():
|
|
||||||
disp_template = kwargs['disp_template']
|
|
||||||
kwargs['template'] = disp_template.template
|
|
||||||
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)
|
|
||||||
|
|
||||||
assert self.template is not None, "Missing template for DisposableVM!"
|
|
||||||
|
|
||||||
if disp_template:
|
|
||||||
self.clone_attrs(disp_template)
|
|
||||||
|
|
||||||
# Use DispVM icon with the same color
|
|
||||||
if self._label:
|
|
||||||
self._label = QubesDispVmLabels[self._label.name]
|
|
||||||
self.icon_path = self._label.icon_path
|
|
||||||
|
|
||||||
@property
|
|
||||||
def type(self):
|
|
||||||
return "DisposableVM"
|
|
||||||
|
|
||||||
def is_disposablevm(self):
|
|
||||||
return True
|
|
||||||
|
|
||||||
@property
|
|
||||||
def ip(self):
|
|
||||||
if self.netvm is not None:
|
|
||||||
return self.netvm.get_ip_for_dispvm(self.dispid)
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def get_clone_attrs(self):
|
|
||||||
attrs = super(QubesDisposableVm, self).get_clone_attrs()
|
|
||||||
attrs.remove('_label')
|
|
||||||
return attrs
|
|
||||||
|
|
||||||
def do_not_use_get_xml_attrs(self):
|
|
||||||
# Minimal set - do not inherit rest of attributes
|
|
||||||
attrs = {}
|
|
||||||
attrs["qid"] = str(self.qid)
|
|
||||||
attrs["name"] = self.name
|
|
||||||
attrs["dispid"] = str(self.dispid)
|
|
||||||
attrs["template_qid"] = str(self.template.qid)
|
|
||||||
attrs["label"] = self.label.name
|
|
||||||
attrs["firewall_conf"] = self.relative_path(self.firewall_conf)
|
|
||||||
attrs["netvm_qid"] = str(self.netvm.qid) if self.netvm is not None else "none"
|
|
||||||
return attrs
|
|
||||||
|
|
||||||
def verify_files(self):
|
|
||||||
return True
|
|
||||||
|
|
||||||
def get_config_params(self):
|
|
||||||
attrs = super(QubesDisposableVm, self).get_config_params()
|
|
||||||
attrs['privatedev'] = ''
|
|
||||||
return attrs
|
|
||||||
|
|
||||||
def create_qubesdb_entries(self):
|
|
||||||
super(QubesDisposableVm, self).create_qubesdb_entries()
|
|
||||||
|
|
||||||
self.qdb.write("/qubes-vm-persistence", "none")
|
|
||||||
self.qdb.write('/qubes-restore-complete', '1')
|
|
||||||
|
|
||||||
def start(self, verbose = False, **kwargs):
|
|
||||||
self.log.debug('start()')
|
|
||||||
if dry_run:
|
|
||||||
return
|
|
||||||
|
|
||||||
# Intentionally not used is_running(): eliminate also "Paused", "Crashed", "Halting"
|
|
||||||
if self.get_power_state() != "Halted":
|
|
||||||
raise QubesException ("VM is already running!")
|
|
||||||
|
|
||||||
if self.netvm is not None:
|
|
||||||
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:
|
|
||||||
print >> sys.stderr, "--> Loading the VM (type = {0})...".format(self.type)
|
|
||||||
|
|
||||||
print >>sys.stderr, "time=%s, creating config file" % (str(time.time()))
|
|
||||||
# refresh config file
|
|
||||||
domain_config = self.create_config_file()
|
|
||||||
|
|
||||||
qmemman_client = self.request_memory()
|
|
||||||
|
|
||||||
# dispvm cannot have PCI devices
|
|
||||||
assert (len(self.pcidevs) == 0), "DispVM cannot have PCI devices"
|
|
||||||
|
|
||||||
print >>sys.stderr, "time=%s, calling restore" % (str(time.time()))
|
|
||||||
vmm.libvirt_conn.restoreFlags(self.disp_savefile,
|
|
||||||
domain_config, libvirt.VIR_DOMAIN_SAVE_PAUSED)
|
|
||||||
|
|
||||||
print >>sys.stderr, "time=%s, done" % (str(time.time()))
|
|
||||||
self._libvirt_domain = None
|
|
||||||
|
|
||||||
if verbose:
|
|
||||||
print >> sys.stderr, "--> Starting Qubes DB..."
|
|
||||||
self.start_qubesdb()
|
|
||||||
|
|
||||||
self.services['qubes-dvm'] = True
|
|
||||||
if verbose:
|
|
||||||
print >> sys.stderr, "--> Setting Qubes DB info for the VM..."
|
|
||||||
self.create_qubesdb_entries()
|
|
||||||
print >>sys.stderr, "time=%s, done qubesdb" % (str(time.time()))
|
|
||||||
|
|
||||||
# fire hooks
|
|
||||||
for hook in self.hooks_start:
|
|
||||||
hook(self, verbose = verbose, **kwargs)
|
|
||||||
|
|
||||||
if verbose:
|
|
||||||
print >> sys.stderr, "--> Starting the VM..."
|
|
||||||
self.libvirt_domain.resume()
|
|
||||||
print >>sys.stderr, "time=%s, resumed" % (str(time.time()))
|
|
||||||
|
|
||||||
# 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
|
|
||||||
# constructing the domain after its main process exits
|
|
||||||
# so we close() when we know the domain is up
|
|
||||||
# the successful unpause is some indicator of it
|
|
||||||
if qmemman_present:
|
|
||||||
qmemman_client.close()
|
|
||||||
|
|
||||||
if kwargs.get('start_guid', True) and \
|
|
||||||
any(os.path.exists(x) for x in GUID_SHMID_FILE):
|
|
||||||
self.start_guid(verbose=verbose, before_qrexec=True,
|
|
||||||
notify_function=kwargs.get('notify_function', None))
|
|
||||||
|
|
||||||
self.start_qrexec_daemon(verbose=verbose,
|
|
||||||
notify_function=kwargs.get('notify_function', None))
|
|
||||||
print >>sys.stderr, "time=%s, qrexec done" % (str(time.time()))
|
|
||||||
|
|
||||||
if kwargs.get('start_guid', True) and \
|
|
||||||
any(os.path.exists(x) for x in GUID_SHMID_FILE):
|
|
||||||
self.start_guid(verbose=verbose,
|
|
||||||
notify_function=kwargs.get('notify_function', None))
|
|
||||||
print >>sys.stderr, "time=%s, guid done" % (str(time.time()))
|
|
||||||
|
|
||||||
return self.xid
|
|
||||||
|
|
||||||
def remove_from_disk(self):
|
|
||||||
# nothing to remove
|
|
||||||
pass
|
|
||||||
|
|
||||||
# register classes
|
|
||||||
register_qubes_vm_class(QubesDisposableVm)
|
|
@ -1,283 +0,0 @@
|
|||||||
#!/usr/bin/python2
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# The Qubes OS Project, http://www.qubes-os.org
|
|
||||||
#
|
|
||||||
# Copyright (C) 2010 Joanna Rutkowska <joanna@invisiblethingslab.com>
|
|
||||||
# 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.
|
|
||||||
#
|
|
||||||
#
|
|
||||||
|
|
||||||
import os
|
|
||||||
import os.path
|
|
||||||
import signal
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
import shutil
|
|
||||||
from xml.etree import ElementTree
|
|
||||||
|
|
||||||
from qubes.qubes import (
|
|
||||||
dry_run,
|
|
||||||
defaults,
|
|
||||||
register_qubes_vm_class,
|
|
||||||
system_path,
|
|
||||||
vmm,
|
|
||||||
QubesException,
|
|
||||||
QubesResizableVm,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
system_path["config_template_hvm"] = '/usr/share/qubes/vm-template-hvm.xml'
|
|
||||||
|
|
||||||
defaults["hvm_disk_size"] = 20*1024*1024*1024
|
|
||||||
defaults["hvm_private_img_size"] = 2*1024*1024*1024
|
|
||||||
defaults["hvm_memory"] = 512
|
|
||||||
|
|
||||||
|
|
||||||
class QubesHVm(QubesResizableVm):
|
|
||||||
"""
|
|
||||||
A class that represents an HVM. A child of QubesVm.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# FIXME: logically should inherit after QubesAppVm, but none of its methods
|
|
||||||
# are useful for HVM
|
|
||||||
|
|
||||||
def get_attrs_config(self):
|
|
||||||
attrs = super(QubesHVm, self).get_attrs_config()
|
|
||||||
attrs.pop('kernel')
|
|
||||||
attrs.pop('kernels_dir')
|
|
||||||
attrs.pop('kernelopts')
|
|
||||||
attrs.pop('uses_default_kernel')
|
|
||||||
attrs.pop('uses_default_kernelopts')
|
|
||||||
attrs['dir_path']['func'] = lambda value: value if value is not None \
|
|
||||||
else os.path.join(system_path["qubes_appvms_dir"], self.name)
|
|
||||||
attrs['config_file_template']['func'] = \
|
|
||||||
lambda x: system_path["config_template_hvm"]
|
|
||||||
attrs['drive'] = { 'attr': '_drive',
|
|
||||||
'save': lambda: str(self.drive) }
|
|
||||||
# Remove this two lines when HVM will get qmemman support
|
|
||||||
attrs['maxmem'].pop('save')
|
|
||||||
attrs['maxmem']['func'] = lambda x: self.memory
|
|
||||||
attrs['timezone'] = { 'default': 'localtime',
|
|
||||||
'save': lambda: str(self.timezone) }
|
|
||||||
attrs['qrexec_installed'] = { 'default': False,
|
|
||||||
'attr': '_qrexec_installed',
|
|
||||||
'save': lambda: str(self._qrexec_installed) }
|
|
||||||
attrs['guiagent_installed'] = { 'default' : False,
|
|
||||||
'attr': '_guiagent_installed',
|
|
||||||
'save': lambda: str(self._guiagent_installed) }
|
|
||||||
attrs['seamless_gui_mode'] = { 'default': False,
|
|
||||||
'attr': '_seamless_gui_mode',
|
|
||||||
'save': lambda: str(self._seamless_gui_mode) }
|
|
||||||
attrs['services']['default'] = "{'meminfo-writer': False}"
|
|
||||||
|
|
||||||
return attrs
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def is_template_compatible(cls, template):
|
|
||||||
if template and (not template.is_template() or template.type != "TemplateHVM"):
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
def get_clone_attrs(self):
|
|
||||||
attrs = super(QubesHVm, self).get_clone_attrs()
|
|
||||||
attrs.remove('kernel')
|
|
||||||
attrs.remove('uses_default_kernel')
|
|
||||||
attrs.remove('kernelopts')
|
|
||||||
attrs.remove('uses_default_kernelopts')
|
|
||||||
attrs += [ 'timezone' ]
|
|
||||||
attrs += [ 'qrexec_installed' ]
|
|
||||||
attrs += [ 'guiagent_installed' ]
|
|
||||||
return attrs
|
|
||||||
|
|
||||||
@property
|
|
||||||
def seamless_gui_mode(self):
|
|
||||||
if not self.guiagent_installed:
|
|
||||||
return False
|
|
||||||
return self._seamless_gui_mode
|
|
||||||
|
|
||||||
@seamless_gui_mode.setter
|
|
||||||
def seamless_gui_mode(self, value):
|
|
||||||
if self._seamless_gui_mode == value:
|
|
||||||
return
|
|
||||||
if not self.guiagent_installed and value:
|
|
||||||
raise ValueError("Seamless GUI mode requires GUI agent installed")
|
|
||||||
|
|
||||||
self._seamless_gui_mode = value
|
|
||||||
if self.is_running():
|
|
||||||
self.send_gui_mode()
|
|
||||||
|
|
||||||
@property
|
|
||||||
def drive(self):
|
|
||||||
return self._drive
|
|
||||||
|
|
||||||
@drive.setter
|
|
||||||
def drive(self, value):
|
|
||||||
if value is None:
|
|
||||||
self._drive = None
|
|
||||||
return
|
|
||||||
|
|
||||||
# strip type for a moment
|
|
||||||
drv_type = "cdrom"
|
|
||||||
if value.startswith("hd:") or value.startswith("cdrom:"):
|
|
||||||
(drv_type, unused, value) = value.partition(":")
|
|
||||||
drv_type = drv_type.lower()
|
|
||||||
|
|
||||||
# sanity check
|
|
||||||
if drv_type not in ['hd', 'cdrom']:
|
|
||||||
raise QubesException("Unsupported drive type: %s" % type)
|
|
||||||
|
|
||||||
if value.count(":") == 0:
|
|
||||||
value = "dom0:" + value
|
|
||||||
if value.count(":/") == 0:
|
|
||||||
# FIXME: when Windows backend will be supported, improve this
|
|
||||||
raise QubesException("Drive path must be absolute")
|
|
||||||
|
|
||||||
self._drive = drv_type + ":" + value
|
|
||||||
|
|
||||||
def create_on_disk(self, verbose, source_template = None):
|
|
||||||
self.log.debug('create_on_disk(source_template={!r})'.format(
|
|
||||||
source_template))
|
|
||||||
if dry_run:
|
|
||||||
return
|
|
||||||
|
|
||||||
if source_template is None:
|
|
||||||
source_template = self.template
|
|
||||||
|
|
||||||
# create empty disk
|
|
||||||
self.storage.private_img_size = defaults["hvm_private_img_size"]
|
|
||||||
self.storage.root_img_size = defaults["hvm_disk_size"]
|
|
||||||
self.storage.create_on_disk(verbose, source_template)
|
|
||||||
|
|
||||||
if verbose:
|
|
||||||
print >> sys.stderr, "--> Creating icon symlink: {0} -> {1}".format(self.icon_path, self.label.icon_path)
|
|
||||||
|
|
||||||
try:
|
|
||||||
if hasattr(os, "symlink"):
|
|
||||||
os.symlink (self.label.icon_path, self.icon_path)
|
|
||||||
else:
|
|
||||||
shutil.copy(self.label.icon_path, self.icon_path)
|
|
||||||
except Exception as e:
|
|
||||||
print >> sys.stderr, "WARNING: Failed to set VM icon: %s" % str(e)
|
|
||||||
|
|
||||||
# Make sure that we have UUID allocated
|
|
||||||
self._update_libvirt_domain()
|
|
||||||
|
|
||||||
# fire hooks
|
|
||||||
for hook in self.hooks_create_on_disk:
|
|
||||||
hook(self, verbose, source_template=source_template)
|
|
||||||
|
|
||||||
def get_private_img_sz(self):
|
|
||||||
if not os.path.exists(self.private_img):
|
|
||||||
return 0
|
|
||||||
|
|
||||||
return os.path.getsize(self.private_img)
|
|
||||||
|
|
||||||
def resize_private_img(self, size):
|
|
||||||
assert size >= self.get_private_img_sz(), "Cannot shrink private.img"
|
|
||||||
|
|
||||||
if self.is_running():
|
|
||||||
raise NotImplementedError("Online resize of HVM's private.img not implemented, shutdown the VM first")
|
|
||||||
|
|
||||||
self.storage.resize_private_img(size)
|
|
||||||
|
|
||||||
def run(self, command, **kwargs):
|
|
||||||
if self.qrexec_installed:
|
|
||||||
if 'gui' in kwargs and kwargs['gui']==False:
|
|
||||||
command = "nogui:" + command
|
|
||||||
return super(QubesHVm, self).run(command, **kwargs)
|
|
||||||
else:
|
|
||||||
raise QubesException("Needs qrexec agent installed in VM to use this function. See also qvm-prefs.")
|
|
||||||
|
|
||||||
@property
|
|
||||||
def stubdom_xid(self):
|
|
||||||
if self.xid < 0:
|
|
||||||
return -1
|
|
||||||
|
|
||||||
if vmm.xs is None:
|
|
||||||
return -1
|
|
||||||
|
|
||||||
stubdom_xid_str = vmm.xs.read('', '/local/domain/%d/image/device-model-domid' % self.xid)
|
|
||||||
if stubdom_xid_str is not None:
|
|
||||||
return int(stubdom_xid_str)
|
|
||||||
else:
|
|
||||||
return -1
|
|
||||||
|
|
||||||
def validate_drive_path(self, drive):
|
|
||||||
drive_type, drive_domain, drive_path = drive.split(':', 2)
|
|
||||||
if drive_domain == 'dom0':
|
|
||||||
if not os.path.exists(drive_path):
|
|
||||||
raise QubesException("Invalid drive path '{}'".format(
|
|
||||||
drive_path))
|
|
||||||
|
|
||||||
def start(self, *args, **kwargs):
|
|
||||||
if self.drive:
|
|
||||||
self.validate_drive_path(self.drive)
|
|
||||||
# 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():
|
|
||||||
raise QubesException("Cannot start the HVM while its template is running")
|
|
||||||
try:
|
|
||||||
if 'mem_required' not in kwargs:
|
|
||||||
# Reserve 44MB for stubdomain
|
|
||||||
kwargs['mem_required'] = (self.memory + 44) * 1024 * 1024
|
|
||||||
return super(QubesHVm, self).start(*args, **kwargs)
|
|
||||||
except QubesException as e:
|
|
||||||
capabilities = vmm.libvirt_conn.getCapabilities()
|
|
||||||
tree = ElementTree.fromstring(capabilities)
|
|
||||||
os_types = tree.findall('./guest/os_type')
|
|
||||||
if 'hvm' not in map(lambda x: x.text, os_types):
|
|
||||||
raise QubesException("Cannot start HVM without VT-x/AMD-v enabled")
|
|
||||||
else:
|
|
||||||
raise
|
|
||||||
|
|
||||||
def _cleanup_zombie_domains(self):
|
|
||||||
super(QubesHVm, self)._cleanup_zombie_domains()
|
|
||||||
if not self.is_running():
|
|
||||||
xc_stubdom = self.get_xc_dominfo(name=self.name+'-dm')
|
|
||||||
if xc_stubdom is not None:
|
|
||||||
if xc_stubdom['paused'] == 1:
|
|
||||||
subprocess.call(['xl', 'destroy', str(xc_stubdom['domid'])])
|
|
||||||
if xc_stubdom['dying'] == 1:
|
|
||||||
# GUID still running?
|
|
||||||
guid_pidfile = \
|
|
||||||
'/var/run/qubes/guid-running.%d' % xc_stubdom['domid']
|
|
||||||
if os.path.exists(guid_pidfile):
|
|
||||||
guid_pid = open(guid_pidfile).read().strip()
|
|
||||||
os.kill(int(guid_pid), 15)
|
|
||||||
|
|
||||||
def is_guid_running(self):
|
|
||||||
# If user force the guiagent, is_guid_running will mimic a standard QubesVM
|
|
||||||
if self.guiagent_installed:
|
|
||||||
return super(QubesHVm, self).is_guid_running()
|
|
||||||
else:
|
|
||||||
xid = self.stubdom_xid
|
|
||||||
if xid < 0:
|
|
||||||
return False
|
|
||||||
if not os.path.exists('/var/run/qubes/guid-running.%d' % xid):
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
def is_fully_usable(self):
|
|
||||||
# Running gui-daemon implies also VM running
|
|
||||||
if not self.is_guid_running():
|
|
||||||
return False
|
|
||||||
if self.qrexec_installed and not self.is_qrexec_running():
|
|
||||||
return False
|
|
||||||
return True
|
|
@ -1,110 +0,0 @@
|
|||||||
#!/usr/bin/python2
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# The Qubes OS Project, http://www.qubes-os.org
|
|
||||||
#
|
|
||||||
# Copyright (C) 2010 Joanna Rutkowska <joanna@invisiblethingslab.com>
|
|
||||||
# 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.
|
|
||||||
#
|
|
||||||
#
|
|
||||||
|
|
||||||
import os
|
|
||||||
import os.path
|
|
||||||
import subprocess
|
|
||||||
import stat
|
|
||||||
import sys
|
|
||||||
import re
|
|
||||||
|
|
||||||
from qubes.qubes import QubesHVm,register_qubes_vm_class,dry_run,vmm
|
|
||||||
from qubes.qubes import QubesException,QubesVmCollection
|
|
||||||
from qubes.qubes import system_path,defaults
|
|
||||||
|
|
||||||
class QubesTemplateHVm(QubesHVm):
|
|
||||||
"""
|
|
||||||
A class that represents an HVM template. A child of QubesHVm.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# In which order load this VM type from qubes.xml
|
|
||||||
load_order = 50
|
|
||||||
|
|
||||||
def get_attrs_config(self):
|
|
||||||
attrs_config = super(QubesTemplateHVm, self).get_attrs_config()
|
|
||||||
attrs_config['dir_path']['func'] = \
|
|
||||||
lambda value: value if value is not None else \
|
|
||||||
os.path.join(system_path["qubes_templates_dir"], self.name)
|
|
||||||
attrs_config['label']['default'] = defaults["template_label"]
|
|
||||||
return attrs_config
|
|
||||||
|
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
|
||||||
|
|
||||||
super(QubesTemplateHVm, self).__init__(**kwargs)
|
|
||||||
|
|
||||||
self.appvms = QubesVmCollection()
|
|
||||||
|
|
||||||
@property
|
|
||||||
def type(self):
|
|
||||||
return "TemplateHVM"
|
|
||||||
|
|
||||||
@property
|
|
||||||
def updateable(self):
|
|
||||||
return True
|
|
||||||
|
|
||||||
def is_template(self):
|
|
||||||
return True
|
|
||||||
|
|
||||||
def is_appvm(self):
|
|
||||||
return False
|
|
||||||
|
|
||||||
@property
|
|
||||||
def rootcow_img(self):
|
|
||||||
return self.storage.rootcow_img
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def is_template_compatible(cls, template):
|
|
||||||
if template is None:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def resize_root_img(self, size):
|
|
||||||
for vm in self.appvms.values():
|
|
||||||
if vm.is_running():
|
|
||||||
raise QubesException("Cannot resize root.img while any VM "
|
|
||||||
"based on this tempate is running")
|
|
||||||
return super(QubesTemplateHVm, self).resize_root_img(size)
|
|
||||||
|
|
||||||
def start(self, *args, **kwargs):
|
|
||||||
for vm in self.appvms.values():
|
|
||||||
if vm.is_running():
|
|
||||||
raise QubesException("Cannot start HVM template while VMs based on it are running")
|
|
||||||
return super(QubesTemplateHVm, self).start(*args, **kwargs)
|
|
||||||
|
|
||||||
def commit_changes (self, verbose = False):
|
|
||||||
self.log.debug('commit_changes()')
|
|
||||||
|
|
||||||
if not vmm.offline_mode:
|
|
||||||
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)
|
|
@ -1,15 +0,0 @@
|
|||||||
PYTHON_QUBESMODPATH = $(PYTHON_SITEPATH)/qubes/modules
|
|
||||||
|
|
||||||
all:
|
|
||||||
python -m compileall .
|
|
||||||
python -O -m compileall .
|
|
||||||
|
|
||||||
install:
|
|
||||||
ifndef PYTHON_SITEPATH
|
|
||||||
$(error PYTHON_SITEPATH not defined)
|
|
||||||
endif
|
|
||||||
mkdir -p $(DESTDIR)$(PYTHON_QUBESMODPATH)
|
|
||||||
cp 0*.py $(DESTDIR)$(PYTHON_QUBESMODPATH)
|
|
||||||
cp 0*.py[co] $(DESTDIR)$(PYTHON_QUBESMODPATH)
|
|
||||||
cp __init__.py $(DESTDIR)$(PYTHON_QUBESMODPATH)
|
|
||||||
cp __init__.py[co] $(DESTDIR)$(PYTHON_QUBESMODPATH)
|
|
@ -1,5 +0,0 @@
|
|||||||
This directory contains Qubes core modules. It will be loaded in
|
|
||||||
lexicographical order, use numeric prefix to force load ordering.
|
|
||||||
|
|
||||||
0* - Qubes base modules
|
|
||||||
00* - Qubes core VM classes
|
|
1
core/.gitignore
vendored
1
core/.gitignore
vendored
@ -1 +0,0 @@
|
|||||||
*.pyo
|
|
@ -1,31 +0,0 @@
|
|||||||
OS ?= Linux
|
|
||||||
|
|
||||||
PYTHON_QUBESPATH = $(PYTHON_SITEPATH)/qubes
|
|
||||||
SETTINGS_SUFFIX = $(BACKEND_VMM)-$(OS)
|
|
||||||
|
|
||||||
all:
|
|
||||||
python -m compileall .
|
|
||||||
python -O -m compileall .
|
|
||||||
make -C storage all
|
|
||||||
|
|
||||||
install:
|
|
||||||
ifndef PYTHON_SITEPATH
|
|
||||||
$(error PYTHON_SITEPATH not defined)
|
|
||||||
endif
|
|
||||||
mkdir -p $(DESTDIR)$(PYTHON_QUBESPATH)
|
|
||||||
cp qubes.py $(DESTDIR)$(PYTHON_QUBESPATH)
|
|
||||||
cp qubes.py[co] $(DESTDIR)$(PYTHON_QUBESPATH)
|
|
||||||
cp qubesutils.py $(DESTDIR)$(PYTHON_QUBESPATH)
|
|
||||||
cp qubesutils.py[co] $(DESTDIR)$(PYTHON_QUBESPATH)
|
|
||||||
cp guihelpers.py $(DESTDIR)$(PYTHON_QUBESPATH)
|
|
||||||
cp guihelpers.py[co] $(DESTDIR)$(PYTHON_QUBESPATH)
|
|
||||||
cp backup.py $(DESTDIR)$(PYTHON_QUBESPATH)
|
|
||||||
cp backup.py[co] $(DESTDIR)$(PYTHON_QUBESPATH)
|
|
||||||
ifneq ($(BACKEND_VMM),)
|
|
||||||
if [ -r settings-$(SETTINGS_SUFFIX).py ]; then \
|
|
||||||
cp settings-$(SETTINGS_SUFFIX).py $(DESTDIR)$(PYTHON_QUBESPATH)/settings.py && \
|
|
||||||
cp settings-$(SETTINGS_SUFFIX).pyc $(DESTDIR)$(PYTHON_QUBESPATH)/settings.pyc && \
|
|
||||||
cp settings-$(SETTINGS_SUFFIX).pyo $(DESTDIR)$(PYTHON_QUBESPATH)/settings.pyo; \
|
|
||||||
fi
|
|
||||||
endif
|
|
||||||
make -C storage install
|
|
@ -1,57 +0,0 @@
|
|||||||
#!/usr/bin/python2
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# The Qubes OS Project, http://www.qubes-os.org
|
|
||||||
#
|
|
||||||
# Copyright (C) 2011 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.
|
|
||||||
#
|
|
||||||
#
|
|
||||||
|
|
||||||
import sys
|
|
||||||
from optparse import OptionParser
|
|
||||||
from PyQt4.QtGui import QApplication,QMessageBox
|
|
||||||
|
|
||||||
app = None
|
|
||||||
system_bus = None
|
|
||||||
|
|
||||||
def prepare_app():
|
|
||||||
global app
|
|
||||||
app = QApplication(sys.argv)
|
|
||||||
app.setOrganizationName("The Qubes Project")
|
|
||||||
app.setOrganizationDomain("http://qubes-os.org")
|
|
||||||
app.setApplicationName("Qubes")
|
|
||||||
|
|
||||||
def ask(text, title="Question", yestoall=False):
|
|
||||||
global app
|
|
||||||
if app is None:
|
|
||||||
prepare_app()
|
|
||||||
|
|
||||||
buttons = QMessageBox.Yes | QMessageBox.No
|
|
||||||
if yestoall:
|
|
||||||
buttons |= QMessageBox.YesToAll
|
|
||||||
|
|
||||||
reply = QMessageBox.question(None, title, text, buttons, defaultButton=QMessageBox.Yes)
|
|
||||||
if reply == QMessageBox.Yes:
|
|
||||||
return 0
|
|
||||||
elif reply == QMessageBox.No:
|
|
||||||
return 1
|
|
||||||
elif reply == QMessageBox.YesToAll:
|
|
||||||
return 2
|
|
||||||
else:
|
|
||||||
#?!
|
|
||||||
return 127
|
|
||||||
|
|
@ -1 +0,0 @@
|
|||||||
../core-modules
|
|
187
core/qubes.py
187
core/qubes.py
@ -1,187 +0,0 @@
|
|||||||
#!/usr/bin/python2
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# The Qubes OS Project, http://www.qubes-os.org
|
|
||||||
#
|
|
||||||
# Copyright (C) 2010 Joanna Rutkowska <joanna@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.
|
|
||||||
#
|
|
||||||
#
|
|
||||||
|
|
||||||
|
|
||||||
qubes_base_dir = "/var/lib/qubes"
|
|
||||||
system_path = {
|
|
||||||
'qubes_guid_path': '/usr/bin/qubes-guid',
|
|
||||||
'qrexec_daemon_path': '/usr/lib/qubes/qrexec-daemon',
|
|
||||||
'qrexec_client_path': '/usr/lib/qubes/qrexec-client',
|
|
||||||
'qubesdb_daemon_path': '/usr/sbin/qubesdb-daemon',
|
|
||||||
|
|
||||||
'qubes_base_dir': qubes_base_dir,
|
|
||||||
|
|
||||||
# Relative to qubes_base_dir
|
|
||||||
'qubes_appvms_dir': 'appvms',
|
|
||||||
'qubes_templates_dir': 'vm-templates',
|
|
||||||
'qubes_servicevms_dir': 'servicevms',
|
|
||||||
'qubes_store_filename': 'qubes.xml',
|
|
||||||
'qubes_kernels_base_dir': 'vm-kernels',
|
|
||||||
|
|
||||||
# qubes_icon_dir is obsolete
|
|
||||||
# use QIcon.fromTheme() where applicable
|
|
||||||
'qubes_icon_dir': '/usr/share/icons/hicolor/128x128/devices',
|
|
||||||
|
|
||||||
'qrexec_policy_dir': '/etc/qubes-rpc/policy',
|
|
||||||
|
|
||||||
'config_template_pv': '/usr/share/qubes/vm-template.xml',
|
|
||||||
|
|
||||||
'qubes_pciback_cmd': '/usr/lib/qubes/unbind-pci-device.sh',
|
|
||||||
'prepare_volatile_img_cmd': '/usr/lib/qubes/prepare-volatile-img.sh',
|
|
||||||
}
|
|
||||||
|
|
||||||
vm_files = {
|
|
||||||
'root_img': 'root.img',
|
|
||||||
'rootcow_img': 'root-cow.img',
|
|
||||||
'volatile_img': 'volatile.img',
|
|
||||||
'private_img': 'private.img',
|
|
||||||
'kernels_subdir': 'kernels',
|
|
||||||
'firewall_conf': 'firewall.xml',
|
|
||||||
'whitelisted_appmenus': 'whitelisted-appmenus.list',
|
|
||||||
'updates_stat_file': 'updates.stat',
|
|
||||||
}
|
|
||||||
|
|
||||||
defaults = {
|
|
||||||
'libvirt_uri': 'xen:///',
|
|
||||||
'memory': 400,
|
|
||||||
'kernelopts': "nopat",
|
|
||||||
'kernelopts_pcidevs': "nopat iommu=soft swiotlb=8192",
|
|
||||||
|
|
||||||
'dom0_update_check_interval': 6*3600,
|
|
||||||
|
|
||||||
'private_img_size': 2*1024*1024*1024,
|
|
||||||
'root_img_size': 10*1024*1024*1024,
|
|
||||||
|
|
||||||
'storage_class': None,
|
|
||||||
|
|
||||||
# how long (in sec) to wait for VMs to shutdown,
|
|
||||||
# before killing them (when used qvm-run with --wait option),
|
|
||||||
'shutdown_counter_max': 60,
|
|
||||||
|
|
||||||
'vm_default_netmask': "255.255.255.0",
|
|
||||||
|
|
||||||
# Set later
|
|
||||||
'appvm_label': None,
|
|
||||||
'template_label': None,
|
|
||||||
'servicevm_label': None,
|
|
||||||
}
|
|
||||||
|
|
||||||
qubes_max_qid = 254
|
|
||||||
qubes_max_netid = 254
|
|
||||||
|
|
||||||
##########################################
|
|
||||||
|
|
||||||
def register_qubes_vm_class(vm_class):
|
|
||||||
QubesVmClasses[vm_class.__name__] = vm_class
|
|
||||||
# register class as local for this module - to make it easy to import from
|
|
||||||
# other modules
|
|
||||||
setattr(sys.modules[__name__], vm_class.__name__, vm_class)
|
|
||||||
|
|
||||||
|
|
||||||
class QubesDaemonPidfile(object):
|
|
||||||
def __init__(self, name):
|
|
||||||
self.name = name
|
|
||||||
self.path = "/var/run/qubes/" + name + ".pid"
|
|
||||||
|
|
||||||
def create_pidfile(self):
|
|
||||||
f = open (self.path, 'w')
|
|
||||||
f.write(str(os.getpid()))
|
|
||||||
f.close()
|
|
||||||
|
|
||||||
def pidfile_exists(self):
|
|
||||||
return os.path.exists(self.path)
|
|
||||||
|
|
||||||
def read_pid(self):
|
|
||||||
f = open (self.path)
|
|
||||||
pid = f.read ().strip()
|
|
||||||
f.close()
|
|
||||||
return int(pid)
|
|
||||||
|
|
||||||
def pidfile_is_stale(self):
|
|
||||||
if not self.pidfile_exists():
|
|
||||||
return False
|
|
||||||
|
|
||||||
# check if the pid file is valid...
|
|
||||||
proc_path = "/proc/" + str(self.read_pid()) + "/cmdline"
|
|
||||||
if not os.path.exists (proc_path):
|
|
||||||
print >> sys.stderr, \
|
|
||||||
"Path {0} doesn't exist, assuming stale pidfile.".\
|
|
||||||
format(proc_path)
|
|
||||||
return True
|
|
||||||
|
|
||||||
return False # It's a good pidfile
|
|
||||||
|
|
||||||
def remove_pidfile(self):
|
|
||||||
os.remove (self.path)
|
|
||||||
|
|
||||||
def __enter__ (self):
|
|
||||||
# assumes the pidfile doesn't exist -- you should ensure it before opening the context
|
|
||||||
self.create_pidfile()
|
|
||||||
|
|
||||||
def __exit__ (self, exc_type, exc_val, exc_tb):
|
|
||||||
self.remove_pidfile()
|
|
||||||
return False
|
|
||||||
|
|
||||||
### Initialization code
|
|
||||||
|
|
||||||
# Globally defined lables
|
|
||||||
QubesVmLabels = {
|
|
||||||
"red": QubesVmLabel(1, "0xcc0000", "red" ),
|
|
||||||
"orange": QubesVmLabel(2, "0xf57900", "orange" ),
|
|
||||||
"yellow": QubesVmLabel(3, "0xedd400", "yellow" ),
|
|
||||||
"green": QubesVmLabel(4, "0x73d216", "green" ),
|
|
||||||
"gray": QubesVmLabel(5, "0x555753", "gray" ),
|
|
||||||
"blue": QubesVmLabel(6, "0x3465a4", "blue" ),
|
|
||||||
"purple": QubesVmLabel(7, "0x75507b", "purple" ),
|
|
||||||
"black": QubesVmLabel(8, "0x000000", "black" ),
|
|
||||||
}
|
|
||||||
|
|
||||||
QubesDispVmLabels = {
|
|
||||||
k: QubesVmLabel(index=v.index, color=v.color, name=v.name, dispvm=True)
|
|
||||||
for k, v in QubesVmLabels.iteritems()
|
|
||||||
}
|
|
||||||
|
|
||||||
defaults["appvm_label"] = QubesVmLabels["red"]
|
|
||||||
defaults["template_label"] = QubesVmLabels["black"]
|
|
||||||
defaults["servicevm_label"] = QubesVmLabels["red"]
|
|
||||||
|
|
||||||
|
|
||||||
QubesVmClasses = {}
|
|
||||||
modules_dir = os.path.join(os.path.dirname(__file__), 'modules')
|
|
||||||
for module_file in sorted(os.listdir(modules_dir)):
|
|
||||||
if not module_file.endswith(".py") or module_file == "__init__.py":
|
|
||||||
continue
|
|
||||||
__import__('qubes.modules.%s' % module_file[:-3])
|
|
||||||
|
|
||||||
try:
|
|
||||||
import qubes.settings
|
|
||||||
qubes.settings.apply(system_path, vm_files, defaults)
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
for path_key in system_path.keys():
|
|
||||||
if not os.path.isabs(system_path[path_key]):
|
|
||||||
system_path[path_key] = os.path.join(
|
|
||||||
system_path['qubes_base_dir'], system_path[path_key])
|
|
||||||
|
|
||||||
# vim:sw=4:et:
|
|
@ -1,844 +0,0 @@
|
|||||||
#!/usr/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# The Qubes OS Project, http://www.qubes-os.org
|
|
||||||
#
|
|
||||||
# Copyright (C) 2011 Marek Marczykowski <marmarek@invisiblethingslab.com>
|
|
||||||
# Copyright (C) 2014 Wojciech Porczyk <wojciech@porczyk.eu>
|
|
||||||
#
|
|
||||||
# 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 string
|
|
||||||
import errno
|
|
||||||
from lxml import etree
|
|
||||||
from lxml.etree import ElementTree, SubElement, Element
|
|
||||||
|
|
||||||
from qubes.qubes import QubesException
|
|
||||||
from qubes.qubes import vmm,defaults
|
|
||||||
from qubes.qubes import system_path,vm_files
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
import subprocess
|
|
||||||
import re
|
|
||||||
import time
|
|
||||||
import stat
|
|
||||||
import libvirt
|
|
||||||
from qubes.qdb import QubesDB,Error,DisconnectedError
|
|
||||||
|
|
||||||
import xen.lowlevel.xc
|
|
||||||
import xen.lowlevel.xs
|
|
||||||
|
|
||||||
# all frontends, prefer xvdi
|
|
||||||
# TODO: get this from libvirt driver?
|
|
||||||
AVAILABLE_FRONTENDS = ['xvd'+c for c in
|
|
||||||
string.lowercase[8:]+string.lowercase[:8]]
|
|
||||||
|
|
||||||
class USBProxyNotInstalled(QubesException):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def mbytes_to_kmg(size):
|
|
||||||
if size > 1024:
|
|
||||||
return "%d GiB" % (size/1024)
|
|
||||||
else:
|
|
||||||
return "%d MiB" % size
|
|
||||||
|
|
||||||
def kbytes_to_kmg(size):
|
|
||||||
if size > 1024:
|
|
||||||
return mbytes_to_kmg(size/1024)
|
|
||||||
else:
|
|
||||||
return "%d KiB" % size
|
|
||||||
|
|
||||||
def bytes_to_kmg(size):
|
|
||||||
if size > 1024:
|
|
||||||
return kbytes_to_kmg(size/1024)
|
|
||||||
else:
|
|
||||||
return "%d B" % size
|
|
||||||
|
|
||||||
def size_to_human (size):
|
|
||||||
"""Humane readable size, with 1/10 precission"""
|
|
||||||
if size < 1024:
|
|
||||||
return str (size);
|
|
||||||
elif size < 1024*1024:
|
|
||||||
return str(round(size/1024.0,1)) + ' KiB'
|
|
||||||
elif size < 1024*1024*1024:
|
|
||||||
return str(round(size/(1024.0*1024),1)) + ' MiB'
|
|
||||||
else:
|
|
||||||
return str(round(size/(1024.0*1024*1024),1)) + ' GiB'
|
|
||||||
|
|
||||||
def print_stdout(text):
|
|
||||||
print (text)
|
|
||||||
|
|
||||||
def print_stderr(text):
|
|
||||||
print >> sys.stderr, (text)
|
|
||||||
|
|
||||||
###### Block devices ########
|
|
||||||
|
|
||||||
def block_devid_to_name(devid):
|
|
||||||
major = devid / 256
|
|
||||||
minor = devid % 256
|
|
||||||
|
|
||||||
dev_class = ""
|
|
||||||
if major == 202:
|
|
||||||
dev_class = "xvd"
|
|
||||||
elif major == 8:
|
|
||||||
dev_class = "sd"
|
|
||||||
else:
|
|
||||||
raise QubesException("Unknown device class %d" % major)
|
|
||||||
|
|
||||||
if minor % 16 == 0:
|
|
||||||
return "%s%c" % (dev_class, ord('a')+minor/16)
|
|
||||||
else:
|
|
||||||
return "%s%c%d" % (dev_class, ord('a')+minor/16, minor%16)
|
|
||||||
|
|
||||||
def block_name_to_majorminor(name):
|
|
||||||
# check if it is already devid
|
|
||||||
if isinstance(name, int):
|
|
||||||
return (name / 256, name % 256)
|
|
||||||
if name.isdigit():
|
|
||||||
return (int(name) / 256, int(name) % 256)
|
|
||||||
|
|
||||||
if os.path.exists('/dev/%s' % name):
|
|
||||||
blk_info = os.stat(os.path.realpath('/dev/%s' % name))
|
|
||||||
if stat.S_ISBLK(blk_info.st_mode):
|
|
||||||
return (blk_info.st_rdev / 256, blk_info.st_rdev % 256)
|
|
||||||
|
|
||||||
major = 0
|
|
||||||
minor = 0
|
|
||||||
dXpY_style = False
|
|
||||||
disk = True
|
|
||||||
|
|
||||||
if name.startswith("xvd"):
|
|
||||||
major = 202
|
|
||||||
elif name.startswith("sd"):
|
|
||||||
major = 8
|
|
||||||
elif name.startswith("mmcblk"):
|
|
||||||
dXpY_style = True
|
|
||||||
major = 179
|
|
||||||
elif name.startswith("scd"):
|
|
||||||
disk = False
|
|
||||||
major = 11
|
|
||||||
elif name.startswith("sr"):
|
|
||||||
disk = False
|
|
||||||
major = 11
|
|
||||||
elif name.startswith("loop"):
|
|
||||||
dXpY_style = True
|
|
||||||
disk = False
|
|
||||||
major = 7
|
|
||||||
elif name.startswith("md"):
|
|
||||||
dXpY_style = True
|
|
||||||
major = 9
|
|
||||||
elif name.startswith("dm-"):
|
|
||||||
disk = False
|
|
||||||
major = 253
|
|
||||||
else:
|
|
||||||
# Unknown device
|
|
||||||
return (0, 0)
|
|
||||||
|
|
||||||
if not dXpY_style:
|
|
||||||
name_match = re.match(r"^([a-z]+)([a-z-])([0-9]*)$", name)
|
|
||||||
else:
|
|
||||||
name_match = re.match(r"^([a-z]+)([0-9]*)(?:p([0-9]+))?$", name)
|
|
||||||
if not name_match:
|
|
||||||
raise QubesException("Invalid device name: %s" % name)
|
|
||||||
|
|
||||||
if disk:
|
|
||||||
if dXpY_style:
|
|
||||||
minor = int(name_match.group(2))*8
|
|
||||||
else:
|
|
||||||
minor = (ord(name_match.group(2))-ord('a')) * 16
|
|
||||||
else:
|
|
||||||
minor = 0
|
|
||||||
if name_match.group(3):
|
|
||||||
minor += int(name_match.group(3))
|
|
||||||
|
|
||||||
return (major, minor)
|
|
||||||
|
|
||||||
|
|
||||||
def block_name_to_devid(name):
|
|
||||||
# check if it is already devid
|
|
||||||
if isinstance(name, int):
|
|
||||||
return name
|
|
||||||
if name.isdigit():
|
|
||||||
return int(name)
|
|
||||||
|
|
||||||
(major, minor) = block_name_to_majorminor(name)
|
|
||||||
return major << 8 | minor
|
|
||||||
|
|
||||||
def block_find_unused_frontend(vm = None):
|
|
||||||
assert vm is not None
|
|
||||||
assert vm.is_running()
|
|
||||||
|
|
||||||
xml = vm.libvirt_domain.XMLDesc()
|
|
||||||
parsed_xml = etree.fromstring(xml)
|
|
||||||
used = [target.get('dev', None) for target in
|
|
||||||
parsed_xml.xpath("//domain/devices/disk/target")]
|
|
||||||
for dev in AVAILABLE_FRONTENDS:
|
|
||||||
if dev not in used:
|
|
||||||
return dev
|
|
||||||
return None
|
|
||||||
|
|
||||||
def block_list_vm(vm, system_disks = False):
|
|
||||||
name_re = re.compile(r"^[a-z0-9-]{1,12}$")
|
|
||||||
device_re = re.compile(r"^[a-z0-9/-]{1,64}$")
|
|
||||||
# FIXME: any better idea of desc_re?
|
|
||||||
desc_re = re.compile(r"^.{1,255}$")
|
|
||||||
mode_re = re.compile(r"^[rw]$")
|
|
||||||
|
|
||||||
assert vm is not None
|
|
||||||
|
|
||||||
if not vm.is_running():
|
|
||||||
return []
|
|
||||||
|
|
||||||
devices_list = {}
|
|
||||||
|
|
||||||
try:
|
|
||||||
untrusted_devices = vm.qdb.multiread('/qubes-block-devices/')
|
|
||||||
except Error:
|
|
||||||
vm.refresh()
|
|
||||||
return {}
|
|
||||||
|
|
||||||
def get_dev_item(dev, item):
|
|
||||||
return untrusted_devices.get(
|
|
||||||
'/qubes-block-devices/%s/%s' % (dev, item),
|
|
||||||
None)
|
|
||||||
|
|
||||||
untrusted_devices_names = list(set(map(lambda x: x.split("/")[2],
|
|
||||||
untrusted_devices.keys())))
|
|
||||||
for untrusted_dev_name in untrusted_devices_names:
|
|
||||||
if name_re.match(untrusted_dev_name):
|
|
||||||
dev_name = untrusted_dev_name
|
|
||||||
untrusted_device_size = get_dev_item(dev_name, 'size')
|
|
||||||
untrusted_device_desc = get_dev_item(dev_name, 'desc')
|
|
||||||
untrusted_device_mode = get_dev_item(dev_name, 'mode')
|
|
||||||
untrusted_device_device = get_dev_item(dev_name, 'device')
|
|
||||||
if untrusted_device_desc is None or untrusted_device_mode is None\
|
|
||||||
or untrusted_device_size is None:
|
|
||||||
print >>sys.stderr, "Missing field in %s device parameters" %\
|
|
||||||
dev_name
|
|
||||||
continue
|
|
||||||
if untrusted_device_device is None:
|
|
||||||
untrusted_device_device = '/dev/' + dev_name
|
|
||||||
if not device_re.match(untrusted_device_device):
|
|
||||||
print >> sys.stderr, "Invalid %s device path in VM '%s'" % (
|
|
||||||
dev_name, vm.name)
|
|
||||||
continue
|
|
||||||
device_device = untrusted_device_device
|
|
||||||
if not untrusted_device_size.isdigit():
|
|
||||||
print >> sys.stderr, "Invalid %s device size in VM '%s'" % (
|
|
||||||
dev_name, vm.name)
|
|
||||||
continue
|
|
||||||
device_size = int(untrusted_device_size)
|
|
||||||
if not desc_re.match(untrusted_device_desc):
|
|
||||||
print >> sys.stderr, "Invalid %s device desc in VM '%s'" % (
|
|
||||||
dev_name, vm.name)
|
|
||||||
continue
|
|
||||||
device_desc = untrusted_device_desc
|
|
||||||
if not mode_re.match(untrusted_device_mode):
|
|
||||||
print >> sys.stderr, "Invalid %s device mode in VM '%s'" % (
|
|
||||||
dev_name, vm.name)
|
|
||||||
continue
|
|
||||||
device_mode = untrusted_device_mode
|
|
||||||
|
|
||||||
if not system_disks:
|
|
||||||
if vm.qid == 0 and device_desc.startswith(system_path[
|
|
||||||
"qubes_base_dir"]):
|
|
||||||
continue
|
|
||||||
|
|
||||||
visible_name = "%s:%s" % (vm.name, dev_name)
|
|
||||||
devices_list[visible_name] = {
|
|
||||||
"name": visible_name,
|
|
||||||
"vm": vm.name,
|
|
||||||
"device": device_device,
|
|
||||||
"size": device_size,
|
|
||||||
"desc": device_desc,
|
|
||||||
"mode": device_mode
|
|
||||||
}
|
|
||||||
|
|
||||||
return devices_list
|
|
||||||
|
|
||||||
def block_list(qvmc = None, vm = None, system_disks = False):
|
|
||||||
if vm is not None:
|
|
||||||
if not vm.is_running():
|
|
||||||
return []
|
|
||||||
else:
|
|
||||||
vm_list = [ vm ]
|
|
||||||
else:
|
|
||||||
if qvmc is None:
|
|
||||||
raise QubesException("You must pass either qvm or vm argument")
|
|
||||||
vm_list = qvmc.values()
|
|
||||||
|
|
||||||
devices_list = {}
|
|
||||||
for vm in vm_list:
|
|
||||||
devices_list.update(block_list_vm(vm, system_disks))
|
|
||||||
return devices_list
|
|
||||||
|
|
||||||
def block_check_attached(qvmc, device):
|
|
||||||
"""
|
|
||||||
|
|
||||||
@type qvmc: QubesVmCollection
|
|
||||||
"""
|
|
||||||
if qvmc is None:
|
|
||||||
# TODO: ValueError
|
|
||||||
raise QubesException("You need to pass qvmc argument")
|
|
||||||
|
|
||||||
for vm in qvmc.values():
|
|
||||||
if vm.qid == 0:
|
|
||||||
# Connecting devices to dom0 not supported
|
|
||||||
continue
|
|
||||||
if not vm.is_running():
|
|
||||||
continue
|
|
||||||
try:
|
|
||||||
libvirt_domain = vm.libvirt_domain
|
|
||||||
if libvirt_domain:
|
|
||||||
xml = libvirt_domain.XMLDesc()
|
|
||||||
else:
|
|
||||||
xml = None
|
|
||||||
except libvirt.libvirtError:
|
|
||||||
if vmm.libvirt_conn.virConnGetLastError()[0] == libvirt.VIR_ERR_NO_DOMAIN:
|
|
||||||
xml = None
|
|
||||||
else:
|
|
||||||
raise
|
|
||||||
if xml:
|
|
||||||
parsed_xml = etree.fromstring(xml)
|
|
||||||
disks = parsed_xml.xpath("//domain/devices/disk")
|
|
||||||
for disk in disks:
|
|
||||||
backend_name = 'dom0'
|
|
||||||
if disk.find('backenddomain') is not None:
|
|
||||||
backend_name = disk.find('backenddomain').get('name')
|
|
||||||
source = disk.find('source')
|
|
||||||
if disk.get('type') == 'file':
|
|
||||||
path = source.get('file')
|
|
||||||
elif disk.get('type') == 'block':
|
|
||||||
path = source.get('dev')
|
|
||||||
else:
|
|
||||||
# TODO: logger
|
|
||||||
print >>sys.stderr, "Unknown disk type '%s' attached to " \
|
|
||||||
"VM '%s'" % (source.get('type'),
|
|
||||||
vm.name)
|
|
||||||
continue
|
|
||||||
if backend_name == device['vm'] and (path == device['device']
|
|
||||||
or not path.startswith('/dev/') and path == device[
|
|
||||||
'desc']):
|
|
||||||
return {
|
|
||||||
"frontend": disk.find('target').get('dev'),
|
|
||||||
"vm": vm}
|
|
||||||
return None
|
|
||||||
|
|
||||||
def device_attach_check(vm, backend_vm, device, frontend, mode):
|
|
||||||
""" Checks all the parameters, dies on errors """
|
|
||||||
if not vm.is_running():
|
|
||||||
raise QubesException("VM %s not running" % vm.name)
|
|
||||||
|
|
||||||
if not backend_vm.is_running():
|
|
||||||
raise QubesException("VM %s not running" % backend_vm.name)
|
|
||||||
|
|
||||||
if device['mode'] == 'r' and mode == 'w':
|
|
||||||
raise QubesException("Cannot attach read-only device in read-write "
|
|
||||||
"mode")
|
|
||||||
|
|
||||||
def block_attach(qvmc, vm, device, frontend=None, mode="w", auto_detach=False, wait=True):
|
|
||||||
backend_vm = qvmc.get_vm_by_name(device['vm'])
|
|
||||||
device_attach_check(vm, backend_vm, device, frontend, mode)
|
|
||||||
if frontend is None:
|
|
||||||
frontend = block_find_unused_frontend(vm)
|
|
||||||
if frontend is None:
|
|
||||||
raise QubesException("No unused frontend found")
|
|
||||||
else:
|
|
||||||
# Check if any device attached at this frontend
|
|
||||||
xml = vm.libvirt_domain.XMLDesc()
|
|
||||||
parsed_xml = etree.fromstring(xml)
|
|
||||||
disks = parsed_xml.xpath("//domain/devices/disk/target[@dev='%s']" %
|
|
||||||
frontend)
|
|
||||||
if len(disks):
|
|
||||||
raise QubesException("Frontend %s busy in VM %s, detach it first" % (frontend, vm.name))
|
|
||||||
|
|
||||||
# Check if this device is attached to some domain
|
|
||||||
attached_vm = block_check_attached(qvmc, device)
|
|
||||||
if attached_vm:
|
|
||||||
if auto_detach:
|
|
||||||
block_detach(attached_vm['vm'], attached_vm['frontend'])
|
|
||||||
else:
|
|
||||||
raise QubesException("Device %s from %s already connected to VM "
|
|
||||||
"%s as %s" % (device['device'],
|
|
||||||
backend_vm.name, attached_vm['vm'], attached_vm['frontend']))
|
|
||||||
|
|
||||||
disk = Element("disk")
|
|
||||||
disk.set('type', 'block')
|
|
||||||
disk.set('device', 'disk')
|
|
||||||
SubElement(disk, 'driver').set('name', 'phy')
|
|
||||||
SubElement(disk, 'source').set('dev', device['device'])
|
|
||||||
SubElement(disk, 'target').set('dev', frontend)
|
|
||||||
if backend_vm.qid != 0:
|
|
||||||
SubElement(disk, 'backenddomain').set('name', device['vm'])
|
|
||||||
if mode == "r":
|
|
||||||
SubElement(disk, 'readonly')
|
|
||||||
vm.libvirt_domain.attachDevice(etree.tostring(disk, encoding='utf-8'))
|
|
||||||
try:
|
|
||||||
# trigger watches to update device status
|
|
||||||
# FIXME: this should be removed once libvirt will report such
|
|
||||||
# events itself
|
|
||||||
vm.qdb.write('/qubes-block-devices', '')
|
|
||||||
except Error:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def block_detach(vm, frontend = "xvdi"):
|
|
||||||
|
|
||||||
xml = vm.libvirt_domain.XMLDesc()
|
|
||||||
parsed_xml = etree.fromstring(xml)
|
|
||||||
attached = parsed_xml.xpath("//domain/devices/disk")
|
|
||||||
for disk in attached:
|
|
||||||
if frontend is not None and disk.find('target').get('dev') != frontend:
|
|
||||||
# Not the device we are looking for
|
|
||||||
continue
|
|
||||||
if frontend is None:
|
|
||||||
# ignore system disks
|
|
||||||
if disk.find('domain') == None and \
|
|
||||||
disk.find('source').get('dev').startswith(system_path[
|
|
||||||
"qubes_base_dir"]):
|
|
||||||
continue
|
|
||||||
vm.libvirt_domain.detachDevice(etree.tostring(disk, encoding='utf-8'))
|
|
||||||
try:
|
|
||||||
# trigger watches to update device status
|
|
||||||
# FIXME: this should be removed once libvirt will report such
|
|
||||||
# events itself
|
|
||||||
vm.qdb.write('/qubes-block-devices', '')
|
|
||||||
except Error:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def block_detach_all(vm):
|
|
||||||
""" Detach all non-system devices"""
|
|
||||||
|
|
||||||
block_detach(vm, None)
|
|
||||||
|
|
||||||
####### USB devices ######
|
|
||||||
|
|
||||||
usb_ver_re = re.compile(r"^(1|2)$")
|
|
||||||
usb_device_re = re.compile(r"^[0-9]+-[0-9]+(_[0-9]+)?$")
|
|
||||||
usb_port_re = re.compile(r"^$|^[0-9]+-[0-9]+(\.[0-9]+)?$")
|
|
||||||
usb_desc_re = re.compile(r"^[ -~]{1,255}$")
|
|
||||||
# should match valid VM name
|
|
||||||
usb_connected_to_re = re.compile(r"^[a-zA-Z][a-zA-Z0-9_.-]*$")
|
|
||||||
|
|
||||||
def usb_decode_device_from_qdb(qdb_encoded_device):
|
|
||||||
""" recover actual device name (xenstore doesn't allow dot in key names, so it was translated to underscore) """
|
|
||||||
return qdb_encoded_device.replace('_', '.')
|
|
||||||
|
|
||||||
def usb_encode_device_for_qdb(device):
|
|
||||||
""" encode actual device name (xenstore doesn't allow dot in key names, so translated it into underscore) """
|
|
||||||
return device.replace('.', '_')
|
|
||||||
|
|
||||||
def usb_list_vm(qvmc, vm):
|
|
||||||
if not vm.is_running():
|
|
||||||
return {}
|
|
||||||
|
|
||||||
try:
|
|
||||||
untrusted_devices = vm.qdb.multiread('/qubes-usb-devices/')
|
|
||||||
except Error:
|
|
||||||
vm.refresh()
|
|
||||||
return {}
|
|
||||||
|
|
||||||
def get_dev_item(dev, item):
|
|
||||||
return untrusted_devices.get(
|
|
||||||
'/qubes-usb-devices/%s/%s' % (dev, item),
|
|
||||||
None)
|
|
||||||
|
|
||||||
devices = {}
|
|
||||||
|
|
||||||
untrusted_devices_names = list(set(map(lambda x: x.split("/")[2],
|
|
||||||
untrusted_devices.keys())))
|
|
||||||
for untrusted_dev_name in untrusted_devices_names:
|
|
||||||
if usb_device_re.match(untrusted_dev_name):
|
|
||||||
dev_name = untrusted_dev_name
|
|
||||||
untrusted_device_desc = get_dev_item(dev_name, 'desc')
|
|
||||||
if not usb_desc_re.match(untrusted_device_desc):
|
|
||||||
print >> sys.stderr, "Invalid %s device desc in VM '%s'" % (
|
|
||||||
dev_name, vm.name)
|
|
||||||
continue
|
|
||||||
device_desc = untrusted_device_desc
|
|
||||||
|
|
||||||
untrusted_connected_to = get_dev_item(dev_name, 'connected-to')
|
|
||||||
if untrusted_connected_to:
|
|
||||||
if not usb_connected_to_re.match(untrusted_connected_to):
|
|
||||||
print >>sys.stderr, \
|
|
||||||
"Invalid %s device 'connected-to' in VM '%s'" % (
|
|
||||||
dev_name, vm.name)
|
|
||||||
continue
|
|
||||||
connected_to = qvmc.get_vm_by_name(untrusted_connected_to)
|
|
||||||
if connected_to is None:
|
|
||||||
print >>sys.stderr, \
|
|
||||||
"Device {} appears to be connected to {}, " \
|
|
||||||
"but such VM doesn't exist".format(
|
|
||||||
dev_name, untrusted_connected_to)
|
|
||||||
else:
|
|
||||||
connected_to = None
|
|
||||||
|
|
||||||
device = usb_decode_device_from_qdb(dev_name)
|
|
||||||
|
|
||||||
full_name = vm.name + ':' + device
|
|
||||||
|
|
||||||
devices[full_name] = {
|
|
||||||
'vm': vm,
|
|
||||||
'device': device,
|
|
||||||
'qdb_path': '/qubes-usb-devices/' + dev_name,
|
|
||||||
'name': full_name,
|
|
||||||
'desc': device_desc,
|
|
||||||
'connected-to': connected_to,
|
|
||||||
}
|
|
||||||
return devices
|
|
||||||
|
|
||||||
|
|
||||||
def usb_list(qvmc, vm=None):
|
|
||||||
"""
|
|
||||||
Returns a dictionary of USB devices (for PVUSB backends running in all VM).
|
|
||||||
The dictionary is keyed by 'name' (see below), each element is a dictionary itself:
|
|
||||||
vm = backend domain object
|
|
||||||
device = device ID
|
|
||||||
name = <backend-vm>:<device>
|
|
||||||
desc = description
|
|
||||||
"""
|
|
||||||
if vm is not None:
|
|
||||||
if not vm.is_running():
|
|
||||||
return {}
|
|
||||||
else:
|
|
||||||
vm_list = [vm]
|
|
||||||
else:
|
|
||||||
vm_list = qvmc.values()
|
|
||||||
|
|
||||||
devices_list = {}
|
|
||||||
for vm in vm_list:
|
|
||||||
devices_list.update(usb_list_vm(qvmc, vm))
|
|
||||||
return devices_list
|
|
||||||
|
|
||||||
def usb_check_attached(qvmc, device):
|
|
||||||
"""Reread device attachment status"""
|
|
||||||
vm = device['vm']
|
|
||||||
untrusted_connected_to = vm.qdb.read(
|
|
||||||
'{}/connected-to'.format(device['qdb_path']))
|
|
||||||
if untrusted_connected_to:
|
|
||||||
if not usb_connected_to_re.match(untrusted_connected_to):
|
|
||||||
raise QubesException(
|
|
||||||
"Invalid %s device 'connected-to' in VM '%s'" % (
|
|
||||||
device['device'], vm.name))
|
|
||||||
connected_to = qvmc.get_vm_by_name(untrusted_connected_to)
|
|
||||||
if connected_to is None:
|
|
||||||
print >>sys.stderr, \
|
|
||||||
"Device {} appears to be connected to {}, " \
|
|
||||||
"but such VM doesn't exist".format(
|
|
||||||
device['device'], untrusted_connected_to)
|
|
||||||
else:
|
|
||||||
connected_to = None
|
|
||||||
return connected_to
|
|
||||||
|
|
||||||
def usb_attach(qvmc, vm, device, auto_detach=False, wait=True):
|
|
||||||
if not vm.is_running():
|
|
||||||
raise QubesException("VM {} not running".format(vm.name))
|
|
||||||
|
|
||||||
if not device['vm'].is_running():
|
|
||||||
raise QubesException("VM {} not running".format(device['vm'].name))
|
|
||||||
|
|
||||||
connected_to = usb_check_attached(qvmc, device)
|
|
||||||
if connected_to:
|
|
||||||
if auto_detach:
|
|
||||||
usb_detach(qvmc, device)
|
|
||||||
else:
|
|
||||||
raise QubesException("Device {} already connected, to {}".format(
|
|
||||||
device['name'], connected_to
|
|
||||||
))
|
|
||||||
|
|
||||||
# set qrexec policy to allow this device
|
|
||||||
policy_line = '{} {} allow\n'.format(vm.name, device['vm'].name)
|
|
||||||
policy_path = '/etc/qubes-rpc/policy/qubes.USB+{}'.format(device['device'])
|
|
||||||
policy_exists = os.path.exists(policy_path)
|
|
||||||
if not policy_exists:
|
|
||||||
try:
|
|
||||||
fd = os.open(policy_path, os.O_CREAT | os.O_EXCL | os.O_WRONLY)
|
|
||||||
with os.fdopen(fd, 'w') as f:
|
|
||||||
f.write(policy_line)
|
|
||||||
except OSError as e:
|
|
||||||
if e.errno == errno.EEXIST:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
raise
|
|
||||||
else:
|
|
||||||
with open(policy_path, 'r+') as f:
|
|
||||||
policy = f.readlines()
|
|
||||||
policy.insert(0, policy_line)
|
|
||||||
f.truncate(0)
|
|
||||||
f.seek(0)
|
|
||||||
f.write(''.join(policy))
|
|
||||||
try:
|
|
||||||
# and actual attach
|
|
||||||
p = vm.run_service('qubes.USBAttach', passio_popen=True, user='root')
|
|
||||||
(stdout, stderr) = p.communicate(
|
|
||||||
'{} {}\n'.format(device['vm'].name, device['device']))
|
|
||||||
if p.returncode == 127:
|
|
||||||
raise USBProxyNotInstalled(
|
|
||||||
"qubes-usb-proxy not installed in the VM")
|
|
||||||
elif p.returncode != 0:
|
|
||||||
# TODO: sanitize and include stdout
|
|
||||||
sanitized_stderr = ''.join([c for c in stderr if ord(c) >= 0x20])
|
|
||||||
raise QubesException('Device attach failed: {}'.format(
|
|
||||||
sanitized_stderr))
|
|
||||||
finally:
|
|
||||||
# FIXME: there is a race condition here - some other process might
|
|
||||||
# modify the file in the meantime. This may result in unexpected
|
|
||||||
# denials, but will not allow too much
|
|
||||||
if not policy_exists:
|
|
||||||
os.unlink(policy_path)
|
|
||||||
else:
|
|
||||||
with open(policy_path, 'r+') as f:
|
|
||||||
policy = f.readlines()
|
|
||||||
policy.remove('{} {} allow\n'.format(vm.name, device['vm'].name))
|
|
||||||
f.truncate(0)
|
|
||||||
f.seek(0)
|
|
||||||
f.write(''.join(policy))
|
|
||||||
|
|
||||||
def usb_detach(qvmc, vm, device):
|
|
||||||
connected_to = usb_check_attached(qvmc, device)
|
|
||||||
# detect race conditions; there is still race here, but much smaller
|
|
||||||
if connected_to is None or connected_to.qid != vm.qid:
|
|
||||||
raise QubesException(
|
|
||||||
"Device {} not connected to VM {}".format(
|
|
||||||
device['name'], vm.name))
|
|
||||||
|
|
||||||
p = device['vm'].run_service('qubes.USBDetach', passio_popen=True,
|
|
||||||
user='root')
|
|
||||||
(stdout, stderr) = p.communicate(
|
|
||||||
'{}\n'.format(device['device']))
|
|
||||||
if p.returncode != 0:
|
|
||||||
# TODO: sanitize and include stdout
|
|
||||||
raise QubesException('Device detach failed')
|
|
||||||
|
|
||||||
def usb_detach_all(qvmc, vm):
|
|
||||||
for dev in usb_list(qvmc).values():
|
|
||||||
connected_to = dev['connected-to']
|
|
||||||
if connected_to is not None and connected_to.qid == vm.qid:
|
|
||||||
usb_detach(qvmc, connected_to, dev)
|
|
||||||
|
|
||||||
####### QubesWatch ######
|
|
||||||
|
|
||||||
def only_in_first_list(l1, l2):
|
|
||||||
ret=[]
|
|
||||||
for i in l1:
|
|
||||||
if not i in l2:
|
|
||||||
ret.append(i)
|
|
||||||
return ret
|
|
||||||
|
|
||||||
class QubesWatch(object):
|
|
||||||
def __init__(self):
|
|
||||||
self._qdb = {}
|
|
||||||
self._qdb_events = {}
|
|
||||||
self.block_callback = None
|
|
||||||
self.meminfo_callback = None
|
|
||||||
self.domain_callback = None
|
|
||||||
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,
|
|
||||||
libvirt.VIR_DOMAIN_EVENT_ID_LIFECYCLE,
|
|
||||||
self._domain_list_changed, None)
|
|
||||||
self.libvirt_conn.domainEventRegisterAny(
|
|
||||||
None,
|
|
||||||
libvirt.VIR_DOMAIN_EVENT_ID_DEVICE_REMOVED,
|
|
||||||
self._device_removed, None)
|
|
||||||
# TODO: device attach libvirt event
|
|
||||||
for vm in vmm.libvirt_conn.listAllDomains():
|
|
||||||
try:
|
|
||||||
if vm.isActive():
|
|
||||||
self._register_watches(vm)
|
|
||||||
except libvirt.libvirtError as e:
|
|
||||||
# this will happen if we loose a race with another tool,
|
|
||||||
# which can just remove the domain
|
|
||||||
if e.get_error_code() == libvirt.VIR_ERR_NO_DOMAIN:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
raise
|
|
||||||
# and for dom0
|
|
||||||
self._register_watches(None)
|
|
||||||
|
|
||||||
def _qdb_handler(self, watch, fd, events, domain_name):
|
|
||||||
try:
|
|
||||||
path = self._qdb[domain_name].read_watch()
|
|
||||||
except DisconnectedError:
|
|
||||||
libvirt.virEventRemoveHandle(watch)
|
|
||||||
del(self._qdb_events[domain_name])
|
|
||||||
self._qdb[domain_name].close()
|
|
||||||
del(self._qdb[domain_name])
|
|
||||||
return
|
|
||||||
if path.startswith('/qubes-block-devices'):
|
|
||||||
if self.block_callback is not None:
|
|
||||||
self.block_callback(domain_name)
|
|
||||||
|
|
||||||
|
|
||||||
def setup_block_watch(self, callback):
|
|
||||||
self.block_callback = callback
|
|
||||||
|
|
||||||
def setup_meminfo_watch(self, callback):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def setup_domain_watch(self, callback):
|
|
||||||
self.domain_callback = callback
|
|
||||||
|
|
||||||
def get_meminfo_key(self, xid):
|
|
||||||
return '/local/domain/%s/memory/meminfo' % xid
|
|
||||||
|
|
||||||
def _register_watches(self, libvirt_domain):
|
|
||||||
if libvirt_domain and libvirt_domain.ID() == 0:
|
|
||||||
# don't use libvirt object for dom0, to always have the same
|
|
||||||
# hardcoded "dom0" name
|
|
||||||
libvirt_domain = None
|
|
||||||
if libvirt_domain:
|
|
||||||
name = libvirt_domain.name()
|
|
||||||
if name in self._qdb:
|
|
||||||
return
|
|
||||||
if not libvirt_domain.isActive():
|
|
||||||
return
|
|
||||||
# open separate connection to Qubes DB:
|
|
||||||
# 1. to not confuse pull() with responses to real commands sent from
|
|
||||||
# other threads (like read, write etc) with watch events
|
|
||||||
# 2. to not think whether QubesDB is thread-safe (it isn't)
|
|
||||||
try:
|
|
||||||
self._qdb[name] = QubesDB(name)
|
|
||||||
except Error as e:
|
|
||||||
if e.args[0] != 2:
|
|
||||||
raise
|
|
||||||
libvirt.virEventAddTimeout(500, self._retry_register_watches,
|
|
||||||
libvirt_domain)
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
name = "dom0"
|
|
||||||
if name in self._qdb:
|
|
||||||
return
|
|
||||||
self._qdb[name] = QubesDB(name)
|
|
||||||
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[name].watch_fd(),
|
|
||||||
libvirt.VIR_EVENT_HANDLE_READABLE,
|
|
||||||
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):
|
|
||||||
if libvirt_domain and libvirt_domain.ID() == 0:
|
|
||||||
name = "dom0"
|
|
||||||
else:
|
|
||||||
name = libvirt_domain.name()
|
|
||||||
if name in self._qdb_events:
|
|
||||||
libvirt.virEventRemoveHandle(self._qdb_events[name])
|
|
||||||
del(self._qdb_events[name])
|
|
||||||
if name in self._qdb:
|
|
||||||
self._qdb[name].close()
|
|
||||||
del(self._qdb[name])
|
|
||||||
|
|
||||||
def _domain_list_changed(self, conn, domain, event, reason, param):
|
|
||||||
# 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)
|
|
||||||
elif event == libvirt.VIR_DOMAIN_EVENT_STOPPED:
|
|
||||||
self._unregister_watches(domain)
|
|
||||||
else:
|
|
||||||
# ignore other events for now
|
|
||||||
return None
|
|
||||||
if self.domain_callback:
|
|
||||||
self.domain_callback(name=domain.name(), uuid=domain.UUID())
|
|
||||||
|
|
||||||
def _device_removed(self, conn, domain, device, param):
|
|
||||||
if self.block_callback is not None:
|
|
||||||
self.block_callback(domain.name())
|
|
||||||
|
|
||||||
def watch_loop(self):
|
|
||||||
while True:
|
|
||||||
libvirt.virEventRunDefaultImpl()
|
|
||||||
|
|
||||||
##### 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_DEFAULT_VM_DISABLE_FLAG=\
|
|
||||||
'/var/lib/qubes/updates/vm-default-disable-updates'
|
|
||||||
|
|
||||||
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():
|
|
||||||
if vm.qid == 0:
|
|
||||||
continue
|
|
||||||
if value:
|
|
||||||
vm.services.pop('qubes-update-check', None)
|
|
||||||
if vm.is_running():
|
|
||||||
try:
|
|
||||||
vm.run("systemctl start qubes-update-check.timer",
|
|
||||||
user="root")
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
vm.services['qubes-update-check'] = False
|
|
||||||
if vm.is_running():
|
|
||||||
try:
|
|
||||||
vm.run("systemctl stop qubes-update-check.timer",
|
|
||||||
user="root")
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
def updates_dom0_toggle(qvm_collection, value):
|
|
||||||
if value:
|
|
||||||
if os.path.exists(UPDATES_DOM0_DISABLE_FLAG):
|
|
||||||
os.unlink(UPDATES_DOM0_DISABLE_FLAG)
|
|
||||||
else:
|
|
||||||
open(UPDATES_DOM0_DISABLE_FLAG, "w").close()
|
|
||||||
|
|
||||||
def updates_dom0_status(qvm_collection):
|
|
||||||
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:
|
|
@ -1,11 +0,0 @@
|
|||||||
#!/usr/bin/python2
|
|
||||||
|
|
||||||
from __future__ import absolute_import
|
|
||||||
|
|
||||||
from qubes.storage.xen import XenStorage, XenPool
|
|
||||||
|
|
||||||
|
|
||||||
def apply(system_path, vm_files, defaults):
|
|
||||||
defaults['storage_class'] = XenStorage
|
|
||||||
defaults['pool_drivers'] = {'xen': XenPool}
|
|
||||||
defaults['pool_config'] = {'dir_path': '/var/lib/qubes/'}
|
|
@ -1,24 +0,0 @@
|
|||||||
OS ?= Linux
|
|
||||||
|
|
||||||
SYSCONFDIR ?= /etc
|
|
||||||
PYTHON_QUBESPATH = $(PYTHON_SITEPATH)/qubes
|
|
||||||
|
|
||||||
all:
|
|
||||||
python -m compileall .
|
|
||||||
python -O -m compileall .
|
|
||||||
|
|
||||||
install:
|
|
||||||
ifndef PYTHON_SITEPATH
|
|
||||||
$(error PYTHON_SITEPATH not defined)
|
|
||||||
endif
|
|
||||||
mkdir -p $(DESTDIR)$(PYTHON_QUBESPATH)/storage
|
|
||||||
cp __init__.py $(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),)
|
|
||||||
if [ -r $(BACKEND_VMM).py ]; then \
|
|
||||||
cp $(BACKEND_VMM).py $(DESTDIR)$(PYTHON_QUBESPATH)/storage && \
|
|
||||||
cp $(BACKEND_VMM).py[co] $(DESTDIR)$(PYTHON_QUBESPATH)/storage; \
|
|
||||||
fi
|
|
||||||
endif
|
|
@ -1,48 +0,0 @@
|
|||||||
#!/usr/bin/python -O
|
|
||||||
|
|
||||||
#
|
|
||||||
# The Qubes OS Project, http://www.qubes-os.org
|
|
||||||
#
|
|
||||||
# Copyright (C) 2014 Wojciech Porczyk <wojciech@porczyk.eu>
|
|
||||||
#
|
|
||||||
# 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 subprocess
|
|
||||||
import unittest
|
|
||||||
|
|
||||||
import qubes.qubesutils
|
|
||||||
|
|
||||||
|
|
||||||
class TestCaseFunctionsAndConstants(unittest.TestCase):
|
|
||||||
def check_output_int(self, cmd):
|
|
||||||
return int(subprocess.check_output(cmd).strip().split(None, 1)[0])
|
|
||||||
|
|
||||||
def test_00_BLKSIZE(self):
|
|
||||||
# this may fail on systems without st_blocks
|
|
||||||
self.assertEqual(qubes.qubesutils.BLKSIZE, self.check_output_int(['stat', '-c%B', '.']))
|
|
||||||
|
|
||||||
def test_01_get_size_one(self):
|
|
||||||
# this may fail on systems without st_blocks
|
|
||||||
self.assertEqual(qubes.qubesutils.get_disk_usage_one(os.stat('.')),
|
|
||||||
self.check_output_int(['stat', '-c%b', '.']) * qubes.qubesutils.BLKSIZE)
|
|
||||||
|
|
||||||
def test_02_get_size(self):
|
|
||||||
self.assertEqual(qubes.qubesutils.get_disk_usage('.'),
|
|
||||||
self.check_output_int(['du', '-s', '--block-size=1', '.']))
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
unittest.main()
|
|
2
dispvm/.gitignore
vendored
2
dispvm/.gitignore
vendored
@ -1,2 +0,0 @@
|
|||||||
qubes_restore
|
|
||||||
xenstore-watch
|
|
@ -1,21 +0,0 @@
|
|||||||
UNITDIR ?= /usr/lib/systemd/system
|
|
||||||
|
|
||||||
all:
|
|
||||||
true
|
|
||||||
|
|
||||||
clean:
|
|
||||||
true
|
|
||||||
|
|
||||||
install:
|
|
||||||
mkdir -p $(DESTDIR)/etc/xen/scripts
|
|
||||||
cp block.qubes $(DESTDIR)/etc/xen/scripts
|
|
||||||
mkdir -p $(DESTDIR)/usr/bin $(DESTDIR)/usr/lib/qubes
|
|
||||||
cp qubes-prepare-saved-domain.sh $(DESTDIR)/usr/lib/qubes
|
|
||||||
cp qubes-update-dispvm-savefile-with-progress.sh $(DESTDIR)/usr/lib/qubes
|
|
||||||
cp qfile-daemon-dvm $(DESTDIR)/usr/lib/qubes
|
|
||||||
mkdir -p $(DESTDIR)$(UNITDIR)
|
|
||||||
cp startup-dvm.sh $(DESTDIR)/usr/lib/qubes
|
|
||||||
cp qubes-setupdvm.service $(DESTDIR)$(UNITDIR)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,61 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
HOTPLUG_STORE="/var/run/xen-hotplug/${XENBUS_PATH//\//-}"
|
|
||||||
|
|
||||||
hd_arr[10]=a
|
|
||||||
hd_arr[11]=b
|
|
||||||
hd_arr[12]=c
|
|
||||||
hd_arr[13]=d
|
|
||||||
hd_arr[14]=e
|
|
||||||
hd_arr[15]=f
|
|
||||||
|
|
||||||
hexdigit()
|
|
||||||
{
|
|
||||||
if [ $1 -lt 10 ] ; then
|
|
||||||
RET=$1
|
|
||||||
else
|
|
||||||
RET=${hd_arr[$1]}
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
hexnumber()
|
|
||||||
{
|
|
||||||
hexdigit $(($1/16))
|
|
||||||
ret2=$RET
|
|
||||||
hexdigit $(($1%16))
|
|
||||||
HEXNUMBER="$ret2"$RET
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
process()
|
|
||||||
{
|
|
||||||
if ! [ "x""$1" = "xfile" ] ; then
|
|
||||||
exec flock /var/run/qubes/hotplug-block /etc/xen/scripts/block $ORIG_ARGS
|
|
||||||
fi
|
|
||||||
while true ; do
|
|
||||||
dev=$(losetup -f --show $2)
|
|
||||||
if [ -n "$dev" ] ; then break ; fi
|
|
||||||
done
|
|
||||||
hexnumber ${dev:9:70}
|
|
||||||
xenstore-write "$XENBUS_PATH/node" "$dev" \
|
|
||||||
"$XENBUS_PATH/physical-device" "7:"$HEXNUMBER \
|
|
||||||
"$XENBUS_PATH/hotplug-status" connected
|
|
||||||
echo "$dev" > "$HOTPLUG_STORE-node"
|
|
||||||
echo "file" > "$HOTPLUG_STORE-type"
|
|
||||||
}
|
|
||||||
|
|
||||||
#exec 2>>/tmp/block.$$
|
|
||||||
#set -x
|
|
||||||
export PATH="/sbin:/bin:/usr/bin:/usr/sbin:$PATH"
|
|
||||||
|
|
||||||
XENBUS_PATH="${XENBUS_PATH:?}"
|
|
||||||
if ! [ "$1" = "add" ] || ! [ -f /var/run/qubes/fast-block-attach ] ; then
|
|
||||||
script=$(xenstore-read "$XENBUS_PATH/script")
|
|
||||||
exec flock /var/run/qubes/hotplug-block $script "$@"
|
|
||||||
fi
|
|
||||||
|
|
||||||
ORIG_ARGS="$@"
|
|
||||||
|
|
||||||
vars=$(xenstore-read "$XENBUS_PATH/type" "$XENBUS_PATH/params")
|
|
||||||
process $vars
|
|
||||||
exit 0
|
|
@ -1,200 +0,0 @@
|
|||||||
#!/usr/bin/python2
|
|
||||||
# coding=utf-8
|
|
||||||
#
|
|
||||||
# The Qubes OS Project, http://www.qubes-os.org
|
|
||||||
#
|
|
||||||
# Copyright (C) 2010 Rafal Wojtczuk <rafal@invisiblethingslab.com>
|
|
||||||
# Copyright (C) 2013-2015 Marek Marczykowski-Górecki
|
|
||||||
# <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.
|
|
||||||
#
|
|
||||||
#
|
|
||||||
import os
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
import shutil
|
|
||||||
import time
|
|
||||||
|
|
||||||
from qubes.qubes import QubesVmCollection, QubesException
|
|
||||||
from qubes.qubes import QubesDispVmLabels
|
|
||||||
from qubes.notify import tray_notify, tray_notify_error, tray_notify_init
|
|
||||||
|
|
||||||
|
|
||||||
current_savefile = '/var/run/qubes/current-savefile'
|
|
||||||
current_savefile_vmdir = '/var/lib/qubes/dvmdata/vmdir'
|
|
||||||
|
|
||||||
|
|
||||||
class QfileDaemonDvm:
|
|
||||||
def __init__(self, name):
|
|
||||||
self.name = name
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_disp_templ():
|
|
||||||
vmdir = os.readlink(current_savefile_vmdir)
|
|
||||||
return vmdir.split('/')[-1]
|
|
||||||
|
|
||||||
def do_get_dvm(self):
|
|
||||||
tray_notify("Starting new DispVM...", "red")
|
|
||||||
|
|
||||||
qvm_collection = QubesVmCollection()
|
|
||||||
qvm_collection.lock_db_for_writing()
|
|
||||||
try:
|
|
||||||
|
|
||||||
tar_process = subprocess.Popen(
|
|
||||||
['bsdtar', '-C', current_savefile_vmdir,
|
|
||||||
'-xSUf', os.path.join(current_savefile_vmdir, 'saved-cows.tar')])
|
|
||||||
|
|
||||||
qvm_collection.load()
|
|
||||||
print >>sys.stderr, "time=%s, collection loaded" % (str(time.time()))
|
|
||||||
|
|
||||||
vm = qvm_collection.get_vm_by_name(self.name)
|
|
||||||
if vm is None:
|
|
||||||
sys.stderr.write('Domain ' + self.name + ' does not exist ?')
|
|
||||||
return None
|
|
||||||
label = vm.label
|
|
||||||
if len(sys.argv) > 4 and len(sys.argv[4]) > 0:
|
|
||||||
assert sys.argv[4] in QubesDispVmLabels.keys(), "Invalid label"
|
|
||||||
label = QubesDispVmLabels[sys.argv[4]]
|
|
||||||
disp_templ = self.get_disp_templ()
|
|
||||||
vm_disptempl = qvm_collection.get_vm_by_name(disp_templ)
|
|
||||||
if vm_disptempl is None:
|
|
||||||
sys.stderr.write('Domain ' + disp_templ + ' does not exist ?')
|
|
||||||
return None
|
|
||||||
dispvm = qvm_collection.add_new_vm('QubesDisposableVm',
|
|
||||||
disp_template=vm_disptempl,
|
|
||||||
label=label)
|
|
||||||
print >>sys.stderr, "time=%s, VM created" % (str(time.time()))
|
|
||||||
# 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):
|
|
||||||
shutil.copy(vm.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:
|
|
||||||
assert os.path.exists(sys.argv[5]), "Invalid firewall.conf location"
|
|
||||||
dispvm.firewall_conf = sys.argv[5]
|
|
||||||
if vm.qid != 0:
|
|
||||||
dispvm.uses_default_netvm = False
|
|
||||||
# netvm can be changed before restore,
|
|
||||||
# but cannot be enabled/disabled
|
|
||||||
if (dispvm.netvm is None) == (vm.dispvm_netvm is None):
|
|
||||||
dispvm.netvm = vm.dispvm_netvm
|
|
||||||
# Wait for tar to finish
|
|
||||||
if tar_process.wait() != 0:
|
|
||||||
sys.stderr.write('Failed to unpack saved-cows.tar')
|
|
||||||
return None
|
|
||||||
print >>sys.stderr, "time=%s, VM starting" % (str(time.time()))
|
|
||||||
try:
|
|
||||||
dispvm.start()
|
|
||||||
except (MemoryError, QubesException) as e:
|
|
||||||
tray_notify_error(str(e))
|
|
||||||
raise
|
|
||||||
if vm.qid != 0:
|
|
||||||
# if need to enable/disable netvm, do it while DispVM is alive
|
|
||||||
if (dispvm.netvm is None) != (vm.dispvm_netvm is None):
|
|
||||||
dispvm.netvm = vm.dispvm_netvm
|
|
||||||
print >>sys.stderr, "time=%s, VM started" % (str(time.time()))
|
|
||||||
qvm_collection.save()
|
|
||||||
finally:
|
|
||||||
qvm_collection.unlock_db()
|
|
||||||
# Reload firewall rules
|
|
||||||
print >>sys.stderr, "time=%s, reloading firewall" % (str(time.time()))
|
|
||||||
for vm in qvm_collection.values():
|
|
||||||
if vm.is_proxyvm() and vm.is_running():
|
|
||||||
vm.write_iptables_qubesdb_entry()
|
|
||||||
|
|
||||||
return dispvm
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def dvm_setup_ok():
|
|
||||||
dvmdata_dir = '/var/lib/qubes/dvmdata/'
|
|
||||||
if not os.path.isfile(current_savefile):
|
|
||||||
return False
|
|
||||||
if not os.path.isfile(dvmdata_dir+'default-savefile') or \
|
|
||||||
not os.path.isfile(dvmdata_dir+'savefile-root'):
|
|
||||||
return False
|
|
||||||
dvm_mtime = os.stat(current_savefile).st_mtime
|
|
||||||
root_mtime = os.stat(dvmdata_dir+'savefile-root').st_mtime
|
|
||||||
if dvm_mtime < root_mtime:
|
|
||||||
template_name = os.path.basename(
|
|
||||||
os.path.dirname(os.readlink(dvmdata_dir+'savefile-root')))
|
|
||||||
if subprocess.call(["xl", "domid", template_name],
|
|
||||||
stdout=open(os.devnull, "w")) == 0:
|
|
||||||
tray_notify("For optimum performance, you should not "
|
|
||||||
"start DispVM when its template is running.", "red")
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
def get_dvm(self):
|
|
||||||
if not self.dvm_setup_ok():
|
|
||||||
if os.system("/usr/lib/qubes/"
|
|
||||||
"qubes-update-dispvm-savefile-with-progress.sh"
|
|
||||||
" >/dev/null </dev/null") != 0:
|
|
||||||
tray_notify_error("DVM savefile creation failed")
|
|
||||||
return None
|
|
||||||
return self.do_get_dvm()
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def finish_disposable(name):
|
|
||||||
qvm_collection = QubesVmCollection()
|
|
||||||
qvm_collection.lock_db_for_writing()
|
|
||||||
qvm_collection.load()
|
|
||||||
vm = qvm_collection.get_vm_by_name(name)
|
|
||||||
if vm is None:
|
|
||||||
qvm_collection.unlock_db()
|
|
||||||
return False
|
|
||||||
|
|
||||||
try:
|
|
||||||
vm.force_shutdown()
|
|
||||||
except QubesException:
|
|
||||||
# VM already destroyed
|
|
||||||
pass
|
|
||||||
qvm_collection.pop(vm.qid)
|
|
||||||
qvm_collection.save()
|
|
||||||
qvm_collection.unlock_db()
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
exec_index = sys.argv[1]
|
|
||||||
if exec_index == "FINISH":
|
|
||||||
QfileDaemonDvm.finish_disposable(sys.argv[2])
|
|
||||||
return
|
|
||||||
|
|
||||||
src_vmname = sys.argv[2]
|
|
||||||
user = sys.argv[3]
|
|
||||||
# accessed directly by get_dvm()
|
|
||||||
# sys.argv[4] - override label
|
|
||||||
# sys.argv[5] - override firewall
|
|
||||||
|
|
||||||
print >>sys.stderr, "time=%s, qfile-daemon-dvm init" % (str(time.time()))
|
|
||||||
tray_notify_init()
|
|
||||||
print >>sys.stderr, "time=%s, creating DispVM" % (str(time.time()))
|
|
||||||
qfile = QfileDaemonDvm(src_vmname)
|
|
||||||
dispvm = qfile.get_dvm()
|
|
||||||
if dispvm is not None:
|
|
||||||
if exec_index == "LAUNCH":
|
|
||||||
print dispvm.name
|
|
||||||
return
|
|
||||||
|
|
||||||
print >>sys.stderr, "time=%s, starting VM process" % (str(time.time()))
|
|
||||||
subprocess.call(['/usr/lib/qubes/qrexec-client', '-d', dispvm.name,
|
|
||||||
user+':exec /usr/lib/qubes/qubes-rpc-multiplexer ' +
|
|
||||||
exec_index + " " + src_vmname])
|
|
||||||
QfileDaemonDvm.finish_disposable(dispvm.name)
|
|
||||||
|
|
||||||
main()
|
|
@ -1,86 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
set -o pipefail
|
|
||||||
|
|
||||||
get_encoded_script()
|
|
||||||
{
|
|
||||||
ENCODED_SCRIPT=`
|
|
||||||
if [ "$1" == "vm-default" ]; then
|
|
||||||
echo /usr/lib/qubes/dispvm-prerun.sh
|
|
||||||
else
|
|
||||||
cat "$1"
|
|
||||||
fi | base64 -w0` || exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
if [ $# != 2 -a $# != 3 ] ; then
|
|
||||||
echo "usage: $0 domainname savefile_to_be_created [preload script]" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
export PATH=$PATH:/sbin:/usr/sbin
|
|
||||||
if [ $# = 3 ] ; then
|
|
||||||
get_encoded_script $3
|
|
||||||
fi
|
|
||||||
VMDIR=/var/lib/qubes/appvms/$1
|
|
||||||
if ! [ -d $VMDIR ] ; then
|
|
||||||
echo "$VMDIR does not exist ?" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
if ! qvm-start $1 --dvm ; then
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
ID=`virsh -c xen:/// domid $1`
|
|
||||||
echo "Waiting for DVM $1 ..." >&2
|
|
||||||
if [ -n "$ENCODED_SCRIPT" ] ; then
|
|
||||||
qubesdb-write -d $1 /qubes-save-script "$ENCODED_SCRIPT"
|
|
||||||
fi
|
|
||||||
#set -x
|
|
||||||
qubesdb-write -d $1 /qubes-save-request 1
|
|
||||||
qubesdb-watch -d $1 /qubes-used-mem
|
|
||||||
qubesdb-read -d $1 /qubes-gateway | \
|
|
||||||
cut -d . -f 3 | tr -d "\n" > $VMDIR/netvm-id.txt
|
|
||||||
kill `cat /var/run/qubes/guid-running.$ID`
|
|
||||||
# FIXME: get connection URI from core scripts
|
|
||||||
virsh -c xen:/// detach-disk $1 xvdb
|
|
||||||
MEM=$(qubesdb-read -d $1 /qubes-used-mem | grep '^[0-9]\+$' | head -n 1)
|
|
||||||
echo "DVM boot complete, memory used=$MEM. Saving image..." >&2
|
|
||||||
QMEMMAN_STOP=/var/run/qubes/do-not-membalance
|
|
||||||
touch $QMEMMAN_STOP
|
|
||||||
virsh -c xen:/// setmem $1 $MEM
|
|
||||||
# Add some safety margin
|
|
||||||
virsh -c xen:/// setmaxmem $1 $[ $MEM + 1024 ]
|
|
||||||
# Stop qubesdb daemon now, so VM can restart it later
|
|
||||||
kill `cat /var/run/qubes/qubesdb.$1.pid`
|
|
||||||
sleep 1
|
|
||||||
touch $2
|
|
||||||
if ! virsh -c xen:/// save $1 $2; then
|
|
||||||
rm -f $QMEMMAN_STOP
|
|
||||||
qvm-kill $1
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
rm -f $QMEMMAN_STOP
|
|
||||||
# Do not allow smaller allocation than 400MB. If that small number comes from
|
|
||||||
# an error, it would prevent further savefile regeneration (because VM would
|
|
||||||
# not start with too little memory). Also 'maxmem' depends on 'memory', so
|
|
||||||
# 400MB is sane compromise.
|
|
||||||
if [ "$MEM" -lt 409600 ]; then
|
|
||||||
qvm-prefs -s $1 memory 400
|
|
||||||
else
|
|
||||||
qvm-prefs -s $1 memory $[ $MEM / 1024 ]
|
|
||||||
fi
|
|
||||||
ln -snf $VMDIR /var/lib/qubes/dvmdata/vmdir
|
|
||||||
cd $VMDIR
|
|
||||||
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 || exit 1
|
|
||||||
else
|
|
||||||
errors=`bsdtar -cSf saved-cows.tar volatile.img 2>&1`
|
|
||||||
if [ -n "$errors" ]; then
|
|
||||||
echo "Failed to create saved-cows.tar: $errors" >&2
|
|
||||||
rm -f saved-cows.tar
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
echo "DVM savefile created successfully."
|
|
@ -1,12 +0,0 @@
|
|||||||
[Unit]
|
|
||||||
Description=Qubes DispVM startup setup
|
|
||||||
After=qubes-core.service
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
Type=oneshot
|
|
||||||
ExecStart=/usr/lib/qubes/startup-dvm.sh
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
||||||
# Cover legacy init.d script
|
|
||||||
Alias=qubes_setupdvm.service
|
|
@ -1,23 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
line1="<b>Please wait (up to 120s) while the DispVM savefile is being updated.</b>"
|
|
||||||
line2="<i><small>This only happens when you have updated the template.</small></i>"
|
|
||||||
line3="<i><small>Next time will be much faster.</small></i>"
|
|
||||||
|
|
||||||
if [ -n "$KDE_FULL_SESSION" ]; then
|
|
||||||
br="<br/>"
|
|
||||||
else
|
|
||||||
br="
|
|
||||||
"
|
|
||||||
fi
|
|
||||||
notify-send --icon=/usr/share/qubes/icons/qubes.png --expire-time=120000 \
|
|
||||||
"Updating default DispVM savefile" "$line1$br$line2$br$line3"
|
|
||||||
|
|
||||||
ret=0
|
|
||||||
|
|
||||||
rm -f /var/run/qubes/qvm-create-default-dvm.stdout
|
|
||||||
if ! qvm-create-default-dvm --used-template --default-script >/var/run/qubes/qvm-create-default-dvm.stdout </dev/null ; then
|
|
||||||
ret=1
|
|
||||||
fi
|
|
||||||
|
|
||||||
exit $ret
|
|
@ -1,22 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
# Setup DispVM things at Qubes system startup
|
|
||||||
|
|
||||||
printf "\x00\x00\x00\x00" > /var/run/qubes/dispVM.seq
|
|
||||||
chown root:qubes /var/run/qubes/dispVM.seq
|
|
||||||
chmod 660 /var/run/qubes/dispVM.seq
|
|
||||||
DEFAULT=/var/lib/qubes/dvmdata/default-savefile
|
|
||||||
# setup DispVM files only when they exists
|
|
||||||
if [ -r $DEFAULT ]; then
|
|
||||||
if [ -f /var/lib/qubes/dvmdata/dont-use-shm ] ; then
|
|
||||||
ln -s $DEFAULT /var/run/qubes/current-savefile
|
|
||||||
else
|
|
||||||
mkdir -m 770 /dev/shm/qubes
|
|
||||||
chown root.qubes /dev/shm/qubes
|
|
||||||
cp -a $(readlink $DEFAULT) /dev/shm/qubes/current-savefile
|
|
||||||
chown root.qubes /dev/shm/qubes/current-savefile
|
|
||||||
chmod 660 /dev/shm/qubes/current-savefile
|
|
||||||
ln -s /dev/shm/qubes/current-savefile /var/run/qubes/current-savefile
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
@ -7,7 +7,6 @@ install:
|
|||||||
cp block-snapshot $(DESTDIR)/etc/xen/scripts
|
cp block-snapshot $(DESTDIR)/etc/xen/scripts
|
||||||
ln -s block-snapshot $(DESTDIR)/etc/xen/scripts/block-origin
|
ln -s block-snapshot $(DESTDIR)/etc/xen/scripts/block-origin
|
||||||
install -d $(DESTDIR)/etc/xdg/autostart
|
install -d $(DESTDIR)/etc/xdg/autostart
|
||||||
install -m 0644 qubes-guid.desktop $(DESTDIR)/etc/xdg/autostart/
|
|
||||||
install -m 0644 qrexec-policy-agent.desktop $(DESTDIR)/etc/xdg/autostart/
|
install -m 0644 qrexec-policy-agent.desktop $(DESTDIR)/etc/xdg/autostart/
|
||||||
install -m 0644 -D tmpfiles-qubes.conf $(DESTDIR)/usr/lib/tmpfiles.d/qubes.conf
|
install -m 0644 -D tmpfiles-qubes.conf $(DESTDIR)/usr/lib/tmpfiles.d/qubes.conf
|
||||||
install -d $(DESTDIR)/etc/dbus-1/system.d
|
install -d $(DESTDIR)/etc/dbus-1/system.d
|
||||||
|
@ -1,7 +0,0 @@
|
|||||||
[Desktop Entry]
|
|
||||||
Name=Qubes Guid
|
|
||||||
Comment=Starts Dom0 GUI daemon for Qubes VMs
|
|
||||||
Icon=qubes
|
|
||||||
Exec=qvm-run --all true
|
|
||||||
Terminal=false
|
|
||||||
Type=Application
|
|
@ -1,161 +0,0 @@
|
|||||||
#!/usr/bin/python
|
|
||||||
# -*- encoding: utf8 -*-
|
|
||||||
#
|
|
||||||
# The Qubes OS Project, http://www.qubes-os.org
|
|
||||||
#
|
|
||||||
# Copyright (C) 2012 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 qubes.qubes import QubesVmCollection
|
|
||||||
from qubes.qubes import QubesHost
|
|
||||||
from qubes.qubes import system_path
|
|
||||||
from optparse import OptionParser
|
|
||||||
import subprocess
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
from qubes.qubes import vmm
|
|
||||||
|
|
||||||
|
|
||||||
def handle_vm(vms, label, new_value = None):
|
|
||||||
functions = { # label: [ getter, setter ],
|
|
||||||
'default-netvm': [ 'get_default_netvm', 'set_default_netvm' ],
|
|
||||||
'default-fw-netvm': [ 'get_default_fw_netvm', 'set_default_fw_netvm' ],
|
|
||||||
'default-template': [ 'get_default_template', 'set_default_template' ],
|
|
||||||
'clockvm': [ 'get_clockvm_vm', 'set_clockvm_vm' ],
|
|
||||||
'updatevm': [ 'get_updatevm_vm', 'set_updatevm_vm' ],
|
|
||||||
}
|
|
||||||
assert label in functions.keys()
|
|
||||||
|
|
||||||
if new_value:
|
|
||||||
if new_value == "none":
|
|
||||||
try:
|
|
||||||
vms.__getattribute__(functions[label][1])(None)
|
|
||||||
except Exception as e:
|
|
||||||
print >> sys.stderr, "ERROR: {0}".format(str(e))
|
|
||||||
exit(1)
|
|
||||||
else:
|
|
||||||
vm = vms.get_vm_by_name (new_value)
|
|
||||||
if vm is None:
|
|
||||||
print >> sys.stderr, "A VM with the name '{0}' does not exist in the system.".format(new_value)
|
|
||||||
exit(1)
|
|
||||||
try:
|
|
||||||
vms.__getattribute__(functions[label][1])(vm)
|
|
||||||
except Exception as e:
|
|
||||||
print >> sys.stderr, "ERROR: {0}".format(str(e))
|
|
||||||
exit(1)
|
|
||||||
else:
|
|
||||||
vm = vms.__getattribute__(functions[label][0])()
|
|
||||||
if vm is not None:
|
|
||||||
return vm.name
|
|
||||||
else:
|
|
||||||
return ""
|
|
||||||
|
|
||||||
def handle_kernel(vms, label, new_value = None):
|
|
||||||
if new_value is not None:
|
|
||||||
if not os.path.exists(os.path.join(system_path["qubes_kernels_base_dir"], new_value)):
|
|
||||||
print >> sys.stderr, "Kernel version {0} not installed.".format(new_value)
|
|
||||||
print >> sys.stderr, "Available versions:"
|
|
||||||
for k in os.listdir(system_path["qubes_kernels_base_dir"]):
|
|
||||||
print >> sys.stderr, " -", k
|
|
||||||
exit(1)
|
|
||||||
vms.set_default_kernel(new_value)
|
|
||||||
else:
|
|
||||||
return vms.get_default_kernel()
|
|
||||||
|
|
||||||
preferences = {
|
|
||||||
"default-netvm": handle_vm,
|
|
||||||
"default-fw-netvm": handle_vm,
|
|
||||||
"default-template": handle_vm,
|
|
||||||
"clockvm": handle_vm,
|
|
||||||
"updatevm": handle_vm,
|
|
||||||
"default-kernel": handle_kernel,
|
|
||||||
}
|
|
||||||
|
|
||||||
def do_list(vms):
|
|
||||||
label_width = 18
|
|
||||||
fmt="{{0:<{0}}}: {{1}}".format(label_width)
|
|
||||||
for pref in sorted(preferences.items()):
|
|
||||||
print fmt.format (pref[0], pref[1](vms, pref[0]))
|
|
||||||
|
|
||||||
def main():
|
|
||||||
usage = "usage: %prog [-l]\n"\
|
|
||||||
"usage: %prog [-g] <property>\n"\
|
|
||||||
"usage: %prog [-s] <property> <new-value>\n"\
|
|
||||||
"List/set various global properties."
|
|
||||||
|
|
||||||
parser = OptionParser (usage)
|
|
||||||
parser.add_option ("-l", "--list", action="store_true", dest="do_list", default=False)
|
|
||||||
parser.add_option ("-s", "--set", action="store_true", dest="do_set", default=False)
|
|
||||||
parser.add_option ("-g", "--get", action="store_true", dest="do_get", default=False)
|
|
||||||
|
|
||||||
(options, args) = parser.parse_args ()
|
|
||||||
|
|
||||||
if options.do_list + options.do_set + options.do_get > 1:
|
|
||||||
print >> sys.stderr, "You can provide only one action at once!"
|
|
||||||
exit (1)
|
|
||||||
|
|
||||||
# Select action based on args count:
|
|
||||||
if not options.do_list and not options.do_get and not options.do_set:
|
|
||||||
if (len (args) < 1):
|
|
||||||
options.do_list = True
|
|
||||||
elif (len (args) == 1):
|
|
||||||
options.do_get = True
|
|
||||||
else:
|
|
||||||
options.do_set = True
|
|
||||||
|
|
||||||
vmm.offline_mode = True
|
|
||||||
|
|
||||||
if options.do_set:
|
|
||||||
qvm_collection = QubesVmCollection()
|
|
||||||
qvm_collection.lock_db_for_writing()
|
|
||||||
qvm_collection.load()
|
|
||||||
else:
|
|
||||||
qvm_collection = QubesVmCollection()
|
|
||||||
qvm_collection.lock_db_for_reading()
|
|
||||||
qvm_collection.load()
|
|
||||||
qvm_collection.unlock_db()
|
|
||||||
|
|
||||||
if options.do_set:
|
|
||||||
if len (args) < 2 or args[0] not in preferences.keys():
|
|
||||||
print >> sys.stderr, "You must specify the property and the new value you wish to set..."
|
|
||||||
print >> sys.stderr, "Available properties:"
|
|
||||||
for p in sorted(preferences.keys()):
|
|
||||||
print >> sys.stderr, "--> '{0}'".format(p)
|
|
||||||
exit (1)
|
|
||||||
|
|
||||||
pref = args[0]
|
|
||||||
new_value = args[1]
|
|
||||||
preferences[pref](qvm_collection, pref, new_value)
|
|
||||||
qvm_collection.save()
|
|
||||||
qvm_collection.unlock_db()
|
|
||||||
|
|
||||||
elif options.do_get:
|
|
||||||
if len (args) < 1 or args[0] not in preferences.keys():
|
|
||||||
print >> sys.stderr, "You must specify the property you wish to get..."
|
|
||||||
print >> sys.stderr, "Available properties:"
|
|
||||||
for p in sorted(preferences.keys()):
|
|
||||||
print >> sys.stderr, "--> '{0}'".format(p)
|
|
||||||
exit (1)
|
|
||||||
pref = args[0]
|
|
||||||
print preferences[pref](qvm_collection, pref)
|
|
||||||
|
|
||||||
else:
|
|
||||||
# do_list
|
|
||||||
do_list(qvm_collection)
|
|
||||||
|
|
||||||
main()
|
|
@ -1,85 +0,0 @@
|
|||||||
#!/usr/bin/python2
|
|
||||||
# -*- encoding: utf8 -*-
|
|
||||||
#
|
|
||||||
# The Qubes OS Project, http://www.qubes-os.org
|
|
||||||
#
|
|
||||||
# Copyright (C) 2014 Marek Marczykowski-Górecki <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 optparse import OptionParser
|
|
||||||
import optparse
|
|
||||||
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
from qubes.qubes import QubesVmCollection
|
|
||||||
from qubes.qubesutils import updates_vms_toggle,updates_dom0_toggle,\
|
|
||||||
updates_dom0_status,updates_vms_status
|
|
||||||
from qubes.qubes import vmm
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
|
|
||||||
usage = "%prog enable|disable|status\n"\
|
|
||||||
" Enable or disable globally checking for updates (both dom0 and VM)"
|
|
||||||
parser = OptionParser (usage)
|
|
||||||
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']:
|
|
||||||
parser.error("Invalid action")
|
|
||||||
|
|
||||||
if options.offline_mode:
|
|
||||||
vmm.offline_mode = True
|
|
||||||
|
|
||||||
qvm_collection = QubesVmCollection()
|
|
||||||
if action == 'status':
|
|
||||||
qvm_collection.lock_db_for_reading()
|
|
||||||
else:
|
|
||||||
qvm_collection.lock_db_for_writing()
|
|
||||||
qvm_collection.load()
|
|
||||||
if action == 'enable':
|
|
||||||
updates_dom0_toggle(qvm_collection, True)
|
|
||||||
updates_vms_toggle(qvm_collection, True)
|
|
||||||
elif action == 'disable':
|
|
||||||
updates_dom0_toggle(qvm_collection, False)
|
|
||||||
updates_vms_toggle(qvm_collection, False)
|
|
||||||
else:
|
|
||||||
if updates_dom0_status(qvm_collection):
|
|
||||||
print "dom0: enabled"
|
|
||||||
else:
|
|
||||||
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':
|
|
||||||
qvm_collection.save()
|
|
||||||
qvm_collection.unlock_db()
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
@ -1,86 +0,0 @@
|
|||||||
#!/usr/bin/python2
|
|
||||||
# -*- encoding: utf8 -*-
|
|
||||||
#
|
|
||||||
# The Qubes OS Project, http://www.qubes-os.org
|
|
||||||
#
|
|
||||||
# Copyright (C) 2010 Joanna Rutkowska <joanna@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 qubes.qubes import QubesVmCollection
|
|
||||||
from qubes.qubes import QubesException
|
|
||||||
from optparse import OptionParser;
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
|
|
||||||
def main():
|
|
||||||
usage = "usage: %prog [options] <appvm-name> <vm-template-name>\n\n"\
|
|
||||||
"Adds an already installed appvm to the Qubes DB\n"\
|
|
||||||
"WARNING: Noramlly you would not need this command,\n"\
|
|
||||||
"and you would use qvm-create instead!"
|
|
||||||
|
|
||||||
parser = OptionParser (usage)
|
|
||||||
parser.add_option ("-p", "--path", dest="dir_path",
|
|
||||||
help="Specify path to the template directory")
|
|
||||||
parser.add_option ("-c", "--conf", dest="conf_file",
|
|
||||||
help="Specify the Xen VM .conf file to use\
|
|
||||||
(relative to the template dir path)")
|
|
||||||
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 ()
|
|
||||||
if (len (args) != 2):
|
|
||||||
parser.error ("You must specify at least the AppVM and TemplateVM names!")
|
|
||||||
vmname = args[0]
|
|
||||||
templatename = args[1]
|
|
||||||
|
|
||||||
if hasattr(os, "geteuid") and os.geteuid() == 0:
|
|
||||||
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, "Retry as unprivileged user."
|
|
||||||
print >> sys.stderr, "... or use --force-root to continue anyway."
|
|
||||||
exit(1)
|
|
||||||
|
|
||||||
qvm_collection = QubesVmCollection()
|
|
||||||
qvm_collection.lock_db_for_writing()
|
|
||||||
qvm_collection.load()
|
|
||||||
|
|
||||||
if qvm_collection.get_vm_by_name(vmname) is not None:
|
|
||||||
print >> sys.stderr, "ERROR: A VM with the name '{0}' already exists in the system.".format(vmname)
|
|
||||||
exit(1)
|
|
||||||
|
|
||||||
template = qvm_collection.get_vm_by_name(templatename)
|
|
||||||
if template is None:
|
|
||||||
print >> sys.stderr, "ERROR: A Template VM with the name '{0}' does not exist in the system.".format(templatename)
|
|
||||||
exit(1)
|
|
||||||
|
|
||||||
|
|
||||||
vm = qvm_collection.add_new_vm("QubesAppVm", name=vmname, template=template,
|
|
||||||
conf_file=options.conf_file,
|
|
||||||
dir_path=options.dir_path)
|
|
||||||
|
|
||||||
try:
|
|
||||||
vm.verify_files()
|
|
||||||
except QubesException as err:
|
|
||||||
print >> sys.stderr, "ERROR: {0}".format(err)
|
|
||||||
qvm_collection.pop(vm.qid)
|
|
||||||
exit (1)
|
|
||||||
|
|
||||||
qvm_collection.save()
|
|
||||||
qvm_collection.unlock_db()
|
|
||||||
|
|
||||||
main()
|
|
@ -1,83 +0,0 @@
|
|||||||
#!/usr/bin/python2
|
|
||||||
# -*- encoding: utf8 -*-
|
|
||||||
#
|
|
||||||
# The Qubes OS Project, http://www.qubes-os.org
|
|
||||||
#
|
|
||||||
# Copyright (C) 2010 Joanna Rutkowska <joanna@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 qubes.qubes import QubesVmCollection,vmm
|
|
||||||
from qubes.qubes import QubesException
|
|
||||||
from optparse import OptionParser;
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
|
|
||||||
def main():
|
|
||||||
usage = "usage: %prog [options] <vm-template-name>\n"\
|
|
||||||
"Adds an already installed template to the Qubes DB"
|
|
||||||
|
|
||||||
parser = OptionParser (usage)
|
|
||||||
parser.add_option ("-p", "--path", dest="dir_path",
|
|
||||||
help="Specify path to the template directory")
|
|
||||||
parser.add_option ("-c", "--conf", dest="conf_file",
|
|
||||||
help="Specify the Xen VM .conf file to use\
|
|
||||||
(relative to the template dir path)")
|
|
||||||
|
|
||||||
parser.add_option ("--rpm", action="store_true", dest="installed_by_rpm",
|
|
||||||
help="Template files have been installed by RPM", default=False)
|
|
||||||
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 ()
|
|
||||||
if (len (args) != 1):
|
|
||||||
parser.error ("You must specify at least the TemplateVM name!")
|
|
||||||
vmname = args[0]
|
|
||||||
|
|
||||||
if hasattr(os, "geteuid") and os.geteuid() == 0:
|
|
||||||
if not options.force_root and not options.installed_by_rpm:
|
|
||||||
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, "... or use --force-root to continue anyway."
|
|
||||||
exit(1)
|
|
||||||
|
|
||||||
vmm.offline_mode = True
|
|
||||||
qvm_collection = QubesVmCollection()
|
|
||||||
qvm_collection.lock_db_for_writing()
|
|
||||||
qvm_collection.load()
|
|
||||||
|
|
||||||
if qvm_collection.get_vm_by_name(vmname) is not None:
|
|
||||||
print >> sys.stderr, "ERROR: A VM with the name '{0}' already exists in the system.".format(vmname)
|
|
||||||
exit(1)
|
|
||||||
|
|
||||||
vm = qvm_collection.add_new_vm("QubesTemplateVm", name=vmname,
|
|
||||||
conf_file=options.conf_file,
|
|
||||||
dir_path=options.dir_path,
|
|
||||||
installed_by_rpm=options.installed_by_rpm)
|
|
||||||
|
|
||||||
try:
|
|
||||||
vm.verify_files()
|
|
||||||
except QubesException as err:
|
|
||||||
print >> sys.stderr, "ERROR: {0}".format(err)
|
|
||||||
qvm_collection.pop(vm.qid)
|
|
||||||
exit (1)
|
|
||||||
|
|
||||||
qvm_collection.save()
|
|
||||||
qvm_collection.unlock_db()
|
|
||||||
|
|
||||||
main()
|
|
@ -1,218 +0,0 @@
|
|||||||
#!/usr/bin/python2
|
|
||||||
# -*- encoding: utf8 -*-
|
|
||||||
#
|
|
||||||
# The Qubes OS Project, http://www.qubes-os.org
|
|
||||||
#
|
|
||||||
# Copyright (C) 2010 Joanna Rutkowska <joanna@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 qubes.qubes import QubesVmCollection
|
|
||||||
from qubes.qubes import QubesException
|
|
||||||
from qubes.backup import backup_prepare, backup_do
|
|
||||||
from qubes.qubesutils import size_to_human
|
|
||||||
from optparse import OptionParser
|
|
||||||
import qubes.backup
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import getpass
|
|
||||||
from locale import getpreferredencoding
|
|
||||||
|
|
||||||
def print_progress(progress):
|
|
||||||
print >> sys.stderr, "\r-> Backing up files: {0}%...".format (progress),
|
|
||||||
|
|
||||||
def main():
|
|
||||||
usage = "usage: %prog [options] <backup-dir-path> [vms-to-be-included ...]"
|
|
||||||
parser = OptionParser (usage)
|
|
||||||
|
|
||||||
parser.add_option ("-x", "--exclude", action="append",
|
|
||||||
dest="exclude_list", default=[],
|
|
||||||
help="Exclude the specified VM from the backup (may be "
|
|
||||||
"repeated)")
|
|
||||||
parser.add_option ("--force-root", action="store_true", dest="force_root", default=False,
|
|
||||||
help="Force to run with root privileges")
|
|
||||||
parser.add_option ("-d", "--dest-vm", action="store", dest="appvm",
|
|
||||||
help="Specify the destination VM to which the backup "
|
|
||||||
"will be sent (implies -e)")
|
|
||||||
parser.add_option ("-e", "--encrypt", action="store_true", dest="encrypt", default=False,
|
|
||||||
help="Encrypt the backup")
|
|
||||||
parser.add_option ("--no-encrypt", action="store_true",
|
|
||||||
dest="no_encrypt", default=False,
|
|
||||||
help="Skip encryption even if sending the backup to a "
|
|
||||||
"VM")
|
|
||||||
parser.add_option ("-p", "--passphrase-file", action="store",
|
|
||||||
dest="pass_file", default=None,
|
|
||||||
help="Read passphrase from a file, or use '-' to read "
|
|
||||||
"from stdin")
|
|
||||||
parser.add_option ("-E", "--enc-algo", action="store",
|
|
||||||
dest="crypto_algorithm", default=None,
|
|
||||||
help="Specify a non-default encryption algorithm. For a "
|
|
||||||
"list of supported algorithms, execute 'openssl "
|
|
||||||
"list-cipher-algorithms' (implies -e)")
|
|
||||||
parser.add_option ("-H", "--hmac-algo", action="store",
|
|
||||||
dest="hmac_algorithm", default=None,
|
|
||||||
help="Specify a non-default HMAC algorithm. For a list "
|
|
||||||
"of supported algorithms, execute 'openssl "
|
|
||||||
"list-message-digest-algorithms'")
|
|
||||||
parser.add_option ("-z", "--compress", action="store_true", dest="compress", default=False,
|
|
||||||
help="Compress the backup")
|
|
||||||
parser.add_option ("-Z", "--compress-filter", action="store",
|
|
||||||
dest="compress_filter", default=False,
|
|
||||||
help="Specify a non-default compression filter program "
|
|
||||||
"(default: gzip)")
|
|
||||||
parser.add_option("--tmpdir", action="store", dest="tmpdir", default=None,
|
|
||||||
help="Specify a temporary directory (if you have at least "
|
|
||||||
"1GB free RAM in dom0, use of /tmp is advised) ("
|
|
||||||
"default: /var/tmp)")
|
|
||||||
parser.add_option ("--debug", action="store_true", dest="debug",
|
|
||||||
default=False, help="Enable (a lot of) debug output")
|
|
||||||
|
|
||||||
(options, args) = parser.parse_args ()
|
|
||||||
|
|
||||||
if (len (args) < 1):
|
|
||||||
print >> sys.stderr, "You must specify the target backup directory "\
|
|
||||||
" (e.g. /mnt/backup)."
|
|
||||||
print >> sys.stderr, "qvm-backup will create a subdirectory there for "\
|
|
||||||
" each individual backup."
|
|
||||||
exit (0)
|
|
||||||
|
|
||||||
base_backup_dir = args[0]
|
|
||||||
|
|
||||||
if hasattr(os, "geteuid") and os.geteuid() == 0:
|
|
||||||
if not options.force_root:
|
|
||||||
print >> sys.stderr, "*** Running this tool as root is strongly "\
|
|
||||||
"discouraged. This will lead to permissions "\
|
|
||||||
"problems."
|
|
||||||
print >> sys.stderr, "Retry as an unprivileged user, or use "\
|
|
||||||
"--force-root to continue anyway."
|
|
||||||
exit(1)
|
|
||||||
|
|
||||||
# Only for locking
|
|
||||||
qvm_collection = QubesVmCollection()
|
|
||||||
qvm_collection.lock_db_for_reading()
|
|
||||||
qvm_collection.load()
|
|
||||||
|
|
||||||
vms = None
|
|
||||||
if (len (args) > 1):
|
|
||||||
vms = [qvm_collection.get_vm_by_name(vmname) for vmname in args[1:]]
|
|
||||||
|
|
||||||
if options.appvm:
|
|
||||||
options.exclude_list.append(options.appvm)
|
|
||||||
|
|
||||||
if options.appvm or options.crypto_algorithm:
|
|
||||||
options.encrypt = True
|
|
||||||
if options.no_encrypt:
|
|
||||||
options.encrypt = False
|
|
||||||
if options.debug:
|
|
||||||
qubes.backup.BACKUP_DEBUG = True
|
|
||||||
files_to_backup = None
|
|
||||||
try:
|
|
||||||
files_to_backup = backup_prepare(
|
|
||||||
vms_list=vms,
|
|
||||||
exclude_list=options.exclude_list,
|
|
||||||
hide_vm_names=options.encrypt)
|
|
||||||
except QubesException as e:
|
|
||||||
print >>sys.stderr, "ERROR: %s" % str(e)
|
|
||||||
exit(1)
|
|
||||||
|
|
||||||
total_backup_sz = reduce(lambda size, file: size+file["size"],
|
|
||||||
files_to_backup, 0)
|
|
||||||
|
|
||||||
if not options.appvm:
|
|
||||||
appvm = None
|
|
||||||
|
|
||||||
if os.path.isdir(base_backup_dir):
|
|
||||||
stat = os.statvfs(base_backup_dir)
|
|
||||||
else:
|
|
||||||
stat = os.statvfs(os.path.dirname(base_backup_dir))
|
|
||||||
backup_fs_free_sz = stat.f_bsize * stat.f_bavail
|
|
||||||
print
|
|
||||||
if (total_backup_sz > backup_fs_free_sz):
|
|
||||||
print >>sys.stderr, "ERROR: Not enough space available on the "\
|
|
||||||
"backup filesystem!"
|
|
||||||
exit(1)
|
|
||||||
|
|
||||||
print "-> Available space: {0}".format(size_to_human(backup_fs_free_sz))
|
|
||||||
else:
|
|
||||||
appvm = qvm_collection.get_vm_by_name(options.appvm)
|
|
||||||
if appvm is None:
|
|
||||||
print >>sys.stderr, "ERROR: VM {0} does not exist!".format(options.appvm)
|
|
||||||
exit(1)
|
|
||||||
|
|
||||||
stat = os.statvfs('/var/tmp')
|
|
||||||
backup_fs_free_sz = stat.f_bsize * stat.f_bavail
|
|
||||||
print
|
|
||||||
if (backup_fs_free_sz < 1000000000):
|
|
||||||
print >>sys.stderr, "ERROR: Not enough space available " \
|
|
||||||
"on the local filesystem (1GB required for temporary files)!"
|
|
||||||
exit(1)
|
|
||||||
|
|
||||||
if not appvm.is_running():
|
|
||||||
appvm.start(verbose=True)
|
|
||||||
|
|
||||||
if options.appvm:
|
|
||||||
print >>sys.stderr, ("NOTE: VM {} will be excluded because it is "
|
|
||||||
"the backup destination.").format(options.appvm)
|
|
||||||
options.exclude_list.append(options.appvm)
|
|
||||||
|
|
||||||
if not options.encrypt:
|
|
||||||
print >>sys.stderr, "WARNING: The backup will NOT be encrypted!"
|
|
||||||
|
|
||||||
if options.pass_file is not None:
|
|
||||||
f = open(options.pass_file) if options.pass_file != "-" else sys.stdin
|
|
||||||
passphrase = f.readline().rstrip()
|
|
||||||
if f is not sys.stdin:
|
|
||||||
f.close()
|
|
||||||
|
|
||||||
else:
|
|
||||||
if raw_input("Do you want to proceed? [y/N] ").upper() != "Y":
|
|
||||||
exit(0)
|
|
||||||
|
|
||||||
s = ("Please enter the passphrase that will be used to {}verify "
|
|
||||||
"the backup: ").format('encrypt and ' if options.encrypt else '')
|
|
||||||
passphrase = getpass.getpass(s)
|
|
||||||
|
|
||||||
if getpass.getpass("Enter again for verification: ") != passphrase:
|
|
||||||
print >>sys.stderr, "ERROR: Passphrase mismatch!"
|
|
||||||
exit(1)
|
|
||||||
|
|
||||||
encoding = sys.stdin.encoding or getpreferredencoding()
|
|
||||||
passphrase = passphrase.decode(encoding)
|
|
||||||
|
|
||||||
kwargs = {}
|
|
||||||
if options.hmac_algorithm:
|
|
||||||
kwargs['hmac_algorithm'] = options.hmac_algorithm
|
|
||||||
if options.crypto_algorithm:
|
|
||||||
kwargs['crypto_algorithm'] = options.crypto_algorithm
|
|
||||||
if options.tmpdir:
|
|
||||||
kwargs['tmpdir'] = options.tmpdir
|
|
||||||
|
|
||||||
try:
|
|
||||||
backup_do(base_backup_dir, files_to_backup, passphrase,
|
|
||||||
progress_callback=print_progress,
|
|
||||||
encrypted=options.encrypt,
|
|
||||||
compressed=options.compress_filter or options.compress,
|
|
||||||
appvm=appvm, **kwargs)
|
|
||||||
except QubesException as e:
|
|
||||||
print >>sys.stderr, "ERROR: %s" % str(e)
|
|
||||||
exit(1)
|
|
||||||
|
|
||||||
print
|
|
||||||
print "-> Backup completed."
|
|
||||||
|
|
||||||
qvm_collection.unlock_db()
|
|
||||||
main()
|
|
@ -1,313 +0,0 @@
|
|||||||
#!/usr/bin/python2
|
|
||||||
# -*- encoding: utf8 -*-
|
|
||||||
#
|
|
||||||
# The Qubes OS Project, http://www.qubes-os.org
|
|
||||||
#
|
|
||||||
# Copyright (C) 2010 Joanna Rutkowska <joanna@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 multiprocessing import Event
|
|
||||||
|
|
||||||
from qubes.qubes import QubesVmCollection
|
|
||||||
from qubes.qubes import QubesException
|
|
||||||
from qubes.backup import backup_restore_header
|
|
||||||
from qubes.backup import backup_restore_prepare
|
|
||||||
from qubes.backup import backup_restore_print_summary
|
|
||||||
from qubes.backup import backup_restore_do
|
|
||||||
import qubes.backup
|
|
||||||
import sys
|
|
||||||
from optparse import OptionParser
|
|
||||||
from locale import getpreferredencoding
|
|
||||||
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import getpass
|
|
||||||
|
|
||||||
def main():
|
|
||||||
usage = "usage: %prog [options] <backup-dir> [vms-to-be-restored ...]"
|
|
||||||
parser = OptionParser (usage)
|
|
||||||
|
|
||||||
parser.add_option ("--verify-only", action="store_true",
|
|
||||||
dest="verify_only", default=False,
|
|
||||||
help="Verify backup integrity without restoring any "
|
|
||||||
"data")
|
|
||||||
|
|
||||||
parser.add_option ("--skip-broken", action="store_true", dest="skip_broken", default=False,
|
|
||||||
help="Do not restore VMs that have missing TemplateVMs "
|
|
||||||
"or NetVMs")
|
|
||||||
|
|
||||||
parser.add_option ("--ignore-missing", action="store_true", dest="ignore_missing", default=False,
|
|
||||||
help="Restore VMs even if their associated TemplateVMs "
|
|
||||||
"and NetVMs are missing")
|
|
||||||
|
|
||||||
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")
|
|
||||||
|
|
||||||
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 names")
|
|
||||||
|
|
||||||
parser.add_option ("--force-root", action="store_true", dest="force_root", default=False,
|
|
||||||
help="Force to run with root privileges")
|
|
||||||
|
|
||||||
parser.add_option ("--replace-template", action="append", dest="replace_template", default=[],
|
|
||||||
help="Restore VMs using another TemplateVM; syntax: "
|
|
||||||
"old-template-name:new-template-name (may be "
|
|
||||||
"repeated)")
|
|
||||||
|
|
||||||
parser.add_option ("-x", "--exclude", action="append", dest="exclude", default=[],
|
|
||||||
help="Skip restore of specified VM (may be repeated)")
|
|
||||||
|
|
||||||
parser.add_option ("--skip-dom0-home", action="store_false", dest="dom0_home", default=True,
|
|
||||||
help="Do not restore dom0 user home directory")
|
|
||||||
|
|
||||||
parser.add_option ("--ignore-username-mismatch", action="store_true", dest="ignore_username_mismatch", default=False,
|
|
||||||
help="Ignore dom0 username mismatch when restoring home "
|
|
||||||
"directory")
|
|
||||||
|
|
||||||
parser.add_option ("-d", "--dest-vm", action="store", dest="appvm",
|
|
||||||
help="Specify VM containing the backup to be restored")
|
|
||||||
|
|
||||||
parser.add_option ("-e", "--encrypted", action="store_true", dest="decrypt", default=False,
|
|
||||||
help="The backup is encrypted")
|
|
||||||
|
|
||||||
parser.add_option ("-p", "--passphrase-file", action="store",
|
|
||||||
dest="pass_file", default=None,
|
|
||||||
help="Read passphrase from file, or use '-' to read from stdin")
|
|
||||||
|
|
||||||
parser.add_option ("-z", "--compressed", action="store_true", dest="compressed", default=False,
|
|
||||||
help="The backup is compressed")
|
|
||||||
|
|
||||||
parser.add_option ("--debug", action="store_true", dest="debug",
|
|
||||||
default=False, help="Enable (a lot of) debug output")
|
|
||||||
|
|
||||||
(options, args) = parser.parse_args ()
|
|
||||||
|
|
||||||
if (len (args) < 1):
|
|
||||||
print >> sys.stderr, "You must specify the backup directory "\
|
|
||||||
"(e.g. /mnt/backup/qubes-2010-12-01-235959)"
|
|
||||||
exit (0)
|
|
||||||
|
|
||||||
backup_dir = args[0]
|
|
||||||
vmlist = args[1:]
|
|
||||||
|
|
||||||
#if not os.path.exists (backup_dir):
|
|
||||||
# print >> sys.stderr, "The backup directory doesn't exist!"
|
|
||||||
# exit(1)
|
|
||||||
|
|
||||||
host_collection = QubesVmCollection()
|
|
||||||
host_collection.lock_db_for_writing()
|
|
||||||
host_collection.load()
|
|
||||||
|
|
||||||
restore_options = {}
|
|
||||||
if options.ignore_missing:
|
|
||||||
restore_options['use-default-template'] = True
|
|
||||||
restore_options['use-default-netvm'] = True
|
|
||||||
if options.replace_template:
|
|
||||||
restore_options['replace-template'] = options.replace_template
|
|
||||||
if options.rename_conflicting:
|
|
||||||
restore_options['rename-conflicting'] = True
|
|
||||||
if not options.dom0_home:
|
|
||||||
restore_options['dom0-home'] = False
|
|
||||||
if options.ignore_username_mismatch:
|
|
||||||
restore_options['ignore-username-mismatch'] = True
|
|
||||||
if options.exclude:
|
|
||||||
restore_options['exclude'] = options.exclude
|
|
||||||
if options.verify_only:
|
|
||||||
restore_options['verify-only'] = True
|
|
||||||
if options.debug:
|
|
||||||
qubes.backup.BACKUP_DEBUG = True
|
|
||||||
|
|
||||||
appvm = None
|
|
||||||
if options.appvm is not None:
|
|
||||||
appvm = host_collection.get_vm_by_name(options.appvm)
|
|
||||||
if appvm is None:
|
|
||||||
print >>sys.stderr, "ERROR: VM {0} does not exist".format(options.appvm)
|
|
||||||
exit(1)
|
|
||||||
|
|
||||||
if options.pass_file is not None:
|
|
||||||
f = open(options.pass_file) if options.pass_file != "-" else sys.stdin
|
|
||||||
passphrase = f.readline().rstrip()
|
|
||||||
if f is not sys.stdin:
|
|
||||||
f.close()
|
|
||||||
else:
|
|
||||||
passphrase = getpass.getpass("Please enter the passphrase to verify "
|
|
||||||
"and (if encrypted) decrypt the backup: ")
|
|
||||||
|
|
||||||
encoding = sys.stdin.encoding or getpreferredencoding()
|
|
||||||
passphrase = passphrase.decode(encoding)
|
|
||||||
|
|
||||||
print >> sys.stderr, "Checking backup content..."
|
|
||||||
|
|
||||||
error_detected = Event()
|
|
||||||
def error_callback(message):
|
|
||||||
error_detected.set()
|
|
||||||
print >> sys.stderr, message
|
|
||||||
|
|
||||||
restore_info = None
|
|
||||||
try:
|
|
||||||
restore_info = backup_restore_prepare(
|
|
||||||
backup_dir,
|
|
||||||
passphrase=passphrase,
|
|
||||||
options=restore_options,
|
|
||||||
host_collection=host_collection,
|
|
||||||
encrypted=options.decrypt,
|
|
||||||
compressed=options.compressed,
|
|
||||||
appvm=appvm,
|
|
||||||
error_callback=error_callback)
|
|
||||||
except QubesException as e:
|
|
||||||
print >> sys.stderr, "ERROR: %s" % str(e)
|
|
||||||
exit(1)
|
|
||||||
|
|
||||||
if len(vmlist) > 0:
|
|
||||||
for vm in restore_info.keys():
|
|
||||||
if vm.startswith('$'):
|
|
||||||
continue
|
|
||||||
if not vm in vmlist:
|
|
||||||
restore_info.pop(vm)
|
|
||||||
|
|
||||||
backup_restore_print_summary(restore_info)
|
|
||||||
|
|
||||||
there_are_conflicting_vms = False
|
|
||||||
there_are_missing_templates = False
|
|
||||||
there_are_missing_netvms = False
|
|
||||||
dom0_username_mismatch = False
|
|
||||||
|
|
||||||
for vm_info in restore_info.values():
|
|
||||||
if 'excluded' in vm_info and vm_info['excluded']:
|
|
||||||
continue
|
|
||||||
if 'missing-template' in vm_info.keys():
|
|
||||||
there_are_missing_templates = True
|
|
||||||
if 'missing-netvm' in vm_info.keys():
|
|
||||||
there_are_missing_netvms = True
|
|
||||||
if 'already-exists' in vm_info.keys():
|
|
||||||
there_are_conflicting_vms = True
|
|
||||||
if 'username-mismatch' in vm_info.keys():
|
|
||||||
dom0_username_mismatch = True
|
|
||||||
|
|
||||||
print
|
|
||||||
|
|
||||||
if hasattr(os, "geteuid") and os.geteuid() == 0:
|
|
||||||
print >> sys.stderr, "*** Running this tool as root is strongly "\
|
|
||||||
"discouraged. This will lead to permissions "\
|
|
||||||
"problems."
|
|
||||||
if options.force_root:
|
|
||||||
print >> sys.stderr, "Continuing as commanded. You have been "\
|
|
||||||
"warned."
|
|
||||||
else:
|
|
||||||
print >> sys.stderr, "Retry as an unprivileged user, or use "\
|
|
||||||
"--force-root to continue anyway."
|
|
||||||
exit(1)
|
|
||||||
|
|
||||||
if there_are_conflicting_vms:
|
|
||||||
print >> sys.stderr, "*** There are VMs with conflicting names on the "\
|
|
||||||
"host! ***"
|
|
||||||
if options.skip_conflicting:
|
|
||||||
print >> sys.stderr, "Those VMs will not be restored. The host "\
|
|
||||||
"VMs will NOT be overwritten."
|
|
||||||
else:
|
|
||||||
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 --rename-conflicting to restore " \
|
|
||||||
"those VMs under modified names (with "\
|
|
||||||
"numbers at the end)."
|
|
||||||
exit (1)
|
|
||||||
|
|
||||||
print "The above VMs will be copied and added to your system."
|
|
||||||
print "Exisiting VMs will NOT be removed."
|
|
||||||
|
|
||||||
if there_are_missing_templates:
|
|
||||||
print >> sys.stderr, "*** One or more TemplateVMs are missing on the"\
|
|
||||||
"host! ***"
|
|
||||||
if not (options.skip_broken or options.ignore_missing):
|
|
||||||
print >> sys.stderr, "Install them before proceeding with the "\
|
|
||||||
"restore."
|
|
||||||
print >> sys.stderr, "Or pass: --skip-broken or --ignore-missing."
|
|
||||||
exit (1)
|
|
||||||
elif options.skip_broken:
|
|
||||||
print >> sys.stderr, "Skipping broken entries: VMs that depend on "\
|
|
||||||
"missing TemplateVMs will NOT be restored."
|
|
||||||
elif options.ignore_missing:
|
|
||||||
print >> sys.stderr, "Ignoring missing entries: VMs that depend "\
|
|
||||||
"on missing TemplateVMs will NOT be restored."
|
|
||||||
else:
|
|
||||||
print >> sys.stderr, "INTERNAL ERROR! Please report this to the "\
|
|
||||||
"Qubes OS team!"
|
|
||||||
exit (1)
|
|
||||||
|
|
||||||
if there_are_missing_netvms:
|
|
||||||
print >> sys.stderr, "*** One or more NetVMs are missing on the "\
|
|
||||||
"host! ***"
|
|
||||||
if not (options.skip_broken or options.ignore_missing):
|
|
||||||
print >> sys.stderr, "Install them before proceeding with the "\
|
|
||||||
"restore."
|
|
||||||
print >> sys.stderr, "Or pass: --skip-broken or --ignore-missing."
|
|
||||||
exit (1)
|
|
||||||
elif options.skip_broken:
|
|
||||||
print >> sys.stderr, "Skipping broken entries: VMs that depend on "\
|
|
||||||
"missing NetVMs will NOT be restored."
|
|
||||||
elif options.ignore_missing:
|
|
||||||
print >> sys.stderr, "Ignoring missing entries: VMs that depend "\
|
|
||||||
"on missing NetVMs will NOT be restored."
|
|
||||||
else:
|
|
||||||
print >> sys.stderr, "INTERNAL ERROR! Please report this to the "\
|
|
||||||
"Qubes OS team!"
|
|
||||||
exit (1)
|
|
||||||
|
|
||||||
if 'dom0' in restore_info.keys() and options.dom0_home:
|
|
||||||
if dom0_username_mismatch:
|
|
||||||
print >> sys.stderr, "*** Dom0 username mismatch! This can break "\
|
|
||||||
"some settings! ***"
|
|
||||||
if not options.ignore_username_mismatch:
|
|
||||||
print >> sys.stderr, "Skip restoring the dom0 home directory "\
|
|
||||||
"(--skip-dom0-home), or pass "\
|
|
||||||
"--ignore-username-mismatch to continue "\
|
|
||||||
"anyway."
|
|
||||||
exit(1)
|
|
||||||
else:
|
|
||||||
print >> sys.stderr, "Continuing as directed."
|
|
||||||
print >> sys.stderr, "NOTE: Before restoring the dom0 home directory, "\
|
|
||||||
"a new directory named "\
|
|
||||||
"'home-pre-restore-<current-time>' will be "\
|
|
||||||
"created inside the dom0 home directory. If any "\
|
|
||||||
"restored files conflict with existing files, "\
|
|
||||||
"the existing files will be moved to this new "\
|
|
||||||
"directory."
|
|
||||||
|
|
||||||
if options.pass_file is None:
|
|
||||||
if raw_input("Do you want to proceed? [y/N] ").upper() != "Y":
|
|
||||||
exit(0)
|
|
||||||
|
|
||||||
try:
|
|
||||||
backup_restore_do(restore_info,
|
|
||||||
host_collection=host_collection,
|
|
||||||
error_callback=error_callback)
|
|
||||||
except QubesException as e:
|
|
||||||
print >> sys.stderr, "ERROR: %s" % str(e)
|
|
||||||
|
|
||||||
host_collection.unlock_db()
|
|
||||||
|
|
||||||
if error_detected.is_set():
|
|
||||||
print "-> Completed with errors!"
|
|
||||||
exit(1)
|
|
||||||
else:
|
|
||||||
print "-> Done."
|
|
||||||
main()
|
|
@ -1,152 +0,0 @@
|
|||||||
#!/usr/bin/python2
|
|
||||||
# -*- encoding: utf8 -*-
|
|
||||||
#
|
|
||||||
# The Qubes OS Project, http://www.qubes-os.org
|
|
||||||
#
|
|
||||||
# Copyright (C) 2010 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 qubes.qubes import QubesVmCollection, QubesException
|
|
||||||
from qubes.qubesutils import block_list,block_attach,block_detach,block_detach_all,block_check_attached
|
|
||||||
from qubes.qubesutils import kbytes_to_kmg, bytes_to_kmg
|
|
||||||
from optparse import OptionParser
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
|
|
||||||
def main():
|
|
||||||
usage = "usage: %prog -l [options]\n"\
|
|
||||||
"usage: %prog -a [options] <vm-name> <device-vm-name>:<device>\n"\
|
|
||||||
"usage: %prog -A [options] <vm-name> <file-vm-name>:<file>\n"\
|
|
||||||
"usage: %prog -d [options] <device-vm-name>:<device>\n"\
|
|
||||||
"usage: %prog -d [options] <vm-name>\n"\
|
|
||||||
"List/set VM block devices."
|
|
||||||
|
|
||||||
parser = OptionParser (usage)
|
|
||||||
parser.add_option ("-l", "--list", action="store_true", dest="do_list", default=False)
|
|
||||||
parser.add_option ("-A", "--attach-file", action="store_true", dest="do_file_attach", default=False,
|
|
||||||
help="Attach specified file instead of physical device")
|
|
||||||
parser.add_option ("-a", "--attach", action="store_true", dest="do_attach", default=False)
|
|
||||||
parser.add_option ("-d", "--detach", action="store_true", dest="do_detach", default=False)
|
|
||||||
parser.add_option ("-f", "--frontend", dest="frontend",
|
|
||||||
help="Specify device name at destination VM [default: xvdi]")
|
|
||||||
parser.add_option ("--ro", dest="ro", action="store_true", default=False,
|
|
||||||
help="Force read-only mode")
|
|
||||||
parser.add_option ("--no-auto-detach", dest="auto_detach", action="store_false", default=True,
|
|
||||||
help="Fail when device already connected to other VM")
|
|
||||||
parser.add_option ("--show-system-disks", dest="system_disks", action="store_true", default=False,
|
|
||||||
help="List also system disks")
|
|
||||||
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 ()
|
|
||||||
|
|
||||||
if hasattr(os, "geteuid") and os.geteuid() == 0:
|
|
||||||
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, "Retry as unprivileged user."
|
|
||||||
print >> sys.stderr, "... or use --force-root to continue anyway."
|
|
||||||
exit(1)
|
|
||||||
|
|
||||||
if options.do_file_attach:
|
|
||||||
options.do_attach = True
|
|
||||||
|
|
||||||
if options.do_list + options.do_attach + options.do_detach > 1:
|
|
||||||
print >> sys.stderr, "Only one of -l -a/-A -d is allowed!"
|
|
||||||
exit (1)
|
|
||||||
|
|
||||||
qvm_collection = QubesVmCollection()
|
|
||||||
qvm_collection.lock_db_for_reading()
|
|
||||||
qvm_collection.load()
|
|
||||||
qvm_collection.unlock_db()
|
|
||||||
|
|
||||||
if options.do_attach:
|
|
||||||
if len(args) != 2:
|
|
||||||
parser.error ("You must provide vm name and device!")
|
|
||||||
vm = qvm_collection.get_vm_by_name(args[0])
|
|
||||||
if vm is None:
|
|
||||||
parser.error ("Invalid VM name: %s" % args[0])
|
|
||||||
# FIXME: here we assume that device is always in form "domain:dev", which can be changed in the future
|
|
||||||
if args[1].find(":") < 0:
|
|
||||||
parser.error ("Invalid device syntax (missing VM name): %s" % args[1])
|
|
||||||
if options.do_file_attach:
|
|
||||||
dev = {}
|
|
||||||
(dev['vm'], dev['device']) = args[1].split(":")
|
|
||||||
dev['desc'] = dev['device']
|
|
||||||
dev['mode'] = 'w'
|
|
||||||
else:
|
|
||||||
dev_list = block_list(qvm_collection)
|
|
||||||
if not args[1] in dev_list.keys():
|
|
||||||
parser.error ("Invalid device name: %s" % args[1])
|
|
||||||
dev = dev_list[args[1]]
|
|
||||||
kwargs = {}
|
|
||||||
if options.frontend:
|
|
||||||
kwargs['frontend'] = options.frontend
|
|
||||||
if options.ro:
|
|
||||||
kwargs['mode'] = "r"
|
|
||||||
else:
|
|
||||||
kwargs['mode'] = dev['mode']
|
|
||||||
kwargs['auto_detach'] = options.auto_detach
|
|
||||||
try:
|
|
||||||
block_attach(qvm_collection, vm, dev, **kwargs)
|
|
||||||
except QubesException as e:
|
|
||||||
print >> sys.stderr, "ERROR: %s" % str(e)
|
|
||||||
sys.exit(1)
|
|
||||||
elif options.do_detach:
|
|
||||||
if (len (args) < 1):
|
|
||||||
parser.error ("You must provide device or vm name!")
|
|
||||||
if len(args) > 1:
|
|
||||||
parser.error ("Too many parameters")
|
|
||||||
# Check if provided name is VM
|
|
||||||
vm = qvm_collection.get_vm_by_name(args[0])
|
|
||||||
if vm is not None:
|
|
||||||
kwargs = {}
|
|
||||||
if options.frontend:
|
|
||||||
kwargs['frontend'] = options.frontend
|
|
||||||
block_detach(vm, **kwargs)
|
|
||||||
else:
|
|
||||||
block_detach_all(vm)
|
|
||||||
else:
|
|
||||||
# Maybe device?
|
|
||||||
dev_list = block_list(qvm_collection)
|
|
||||||
if not args[0] in dev_list.keys():
|
|
||||||
parser.error ("Invalid VM or device name: %s" % args[0])
|
|
||||||
dev = dev_list[args[0]]
|
|
||||||
attached_to = block_check_attached(qvm_collection, dev)
|
|
||||||
if attached_to is None:
|
|
||||||
print >> sys.stderr, "WARNING: Device not connected to any VM"
|
|
||||||
exit(0)
|
|
||||||
block_detach(attached_to['vm'], attached_to['frontend'])
|
|
||||||
else:
|
|
||||||
# do_list
|
|
||||||
if len(args) > 0:
|
|
||||||
parser.error ("Too many parameters")
|
|
||||||
kwargs = {}
|
|
||||||
kwargs['qvmc'] = qvm_collection
|
|
||||||
kwargs['system_disks'] = options.system_disks
|
|
||||||
for dev in block_list(**kwargs).values():
|
|
||||||
attached_to = block_check_attached(qvm_collection, dev)
|
|
||||||
attached_to_str = ""
|
|
||||||
if attached_to:
|
|
||||||
attached_to_str = " (attached to '%s' as '%s')" % (
|
|
||||||
attached_to['vm'].name, attached_to['frontend'])
|
|
||||||
size_str = bytes_to_kmg(dev['size'])
|
|
||||||
print "%s\t%s %s%s" % (dev['name'], dev['desc'], size_str, attached_to_str)
|
|
||||||
exit (0)
|
|
||||||
|
|
||||||
main()
|
|
@ -1,81 +0,0 @@
|
|||||||
#!/usr/bin/python2
|
|
||||||
# -*- encoding: utf8 -*-
|
|
||||||
#
|
|
||||||
# The Qubes OS Project, http://www.qubes-os.org
|
|
||||||
#
|
|
||||||
# Copyright (C) 2011 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 qubes.qubes import QubesVmCollection,QubesException
|
|
||||||
from optparse import OptionParser
|
|
||||||
import sys
|
|
||||||
import time
|
|
||||||
|
|
||||||
def main():
|
|
||||||
usage = """usage: %prog [options] <vm-name>\n
|
|
||||||
Specify no state options to check if VM exists"""
|
|
||||||
parser = OptionParser (usage)
|
|
||||||
|
|
||||||
parser.add_option ("-q", "--quiet", action="store_false", dest="verbose", default=True)
|
|
||||||
parser.add_option ("--running", action="store_true", dest="running", default=False,
|
|
||||||
help="Determine if VM is running")
|
|
||||||
parser.add_option ("--paused", action="store_true", dest="paused", default=False,
|
|
||||||
help="Determine if VM is paused")
|
|
||||||
parser.add_option ("--template", action="store_true", dest="template", default=False,
|
|
||||||
help="Determine if VM is a template")
|
|
||||||
|
|
||||||
(options, args) = parser.parse_args ()
|
|
||||||
if (len (args) != 1):
|
|
||||||
parser.error ("You must specify VM name!")
|
|
||||||
|
|
||||||
qvm_collection = QubesVmCollection()
|
|
||||||
qvm_collection.lock_db_for_reading()
|
|
||||||
qvm_collection.load()
|
|
||||||
qvm_collection.unlock_db()
|
|
||||||
|
|
||||||
vmname = args[0]
|
|
||||||
vm = qvm_collection.get_vm_by_name(vmname)
|
|
||||||
if vm is None:
|
|
||||||
if options.verbose:
|
|
||||||
print >> sys.stderr, "A VM with the name '{0}' does not exist in the system!".format(vmname)
|
|
||||||
exit(1)
|
|
||||||
|
|
||||||
elif options.running:
|
|
||||||
vm_state = not vm.is_running()
|
|
||||||
if options.verbose:
|
|
||||||
print >> sys.stderr, "A VM with the name {0} is {1}running.".format(vmname, "not " * vm_state)
|
|
||||||
exit(vm_state)
|
|
||||||
|
|
||||||
elif options.paused:
|
|
||||||
vm_state = not vm.is_paused()
|
|
||||||
if options.verbose:
|
|
||||||
print >> sys.stderr, "A VM with the name {0} is {1}paused.".format(vmname, "not " * vm_state)
|
|
||||||
exit(vm_state)
|
|
||||||
|
|
||||||
elif options.template:
|
|
||||||
vm_state = not vm.is_template()
|
|
||||||
if options.verbose:
|
|
||||||
print >> sys.stderr, "A VM with the name {0} is {1}a template.".format(vmname, "not " * vm_state)
|
|
||||||
exit(vm_state)
|
|
||||||
|
|
||||||
else:
|
|
||||||
if options.verbose:
|
|
||||||
print >> sys.stderr, "A VM with the name '{0}' does exist.".format(vmname)
|
|
||||||
exit(0)
|
|
||||||
|
|
||||||
main()
|
|
@ -1,105 +0,0 @@
|
|||||||
#!/usr/bin/python2
|
|
||||||
# -*- encoding: utf8 -*-
|
|
||||||
#
|
|
||||||
# The Qubes OS Project, http://www.qubes-os.org
|
|
||||||
#
|
|
||||||
# Copyright (C) 2010 Joanna Rutkowska <joanna@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 os
|
|
||||||
import sys
|
|
||||||
from optparse import OptionParser
|
|
||||||
|
|
||||||
from qubes.qubes import QubesVmCollection
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
usage = "usage: %prog [options] <src-name> <new-name>\n"\
|
|
||||||
"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")
|
|
||||||
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]
|
|
||||||
dstname = args[1]
|
|
||||||
|
|
||||||
if hasattr(os, "geteuid") and os.geteuid() == 0:
|
|
||||||
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, "Retry as unprivileged user."
|
|
||||||
print >> sys.stderr, "... or use --force-root to continue anyway."
|
|
||||||
exit(1)
|
|
||||||
|
|
||||||
qvm_collection = QubesVmCollection()
|
|
||||||
qvm_collection.lock_db_for_writing()
|
|
||||||
qvm_collection.load()
|
|
||||||
|
|
||||||
src_vm = qvm_collection.get_vm_by_name(srcname)
|
|
||||||
if src_vm is None:
|
|
||||||
print >> sys.stderr, \
|
|
||||||
"ERROR: A VM with the name '{0}' does not exist in the system." \
|
|
||||||
.format(srcname)
|
|
||||||
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:
|
|
||||||
print >> sys.stderr, \
|
|
||||||
"ERROR: A VM with the name '{0}' already exists in the system." \
|
|
||||||
.format(dstname)
|
|
||||||
exit(1)
|
|
||||||
|
|
||||||
if src_vm.is_disposablevm():
|
|
||||||
print >> sys.stderr, "ERROR: Clone not supported for this type of VM"
|
|
||||||
exit(1)
|
|
||||||
|
|
||||||
dst_vm = qvm_collection.add_new_vm(src_vm.__class__.__name__,
|
|
||||||
name=dstname, template=src_vm.template,
|
|
||||||
pool_name=pool_name,
|
|
||||||
dir_path=options.dir_path,
|
|
||||||
installed_by_rpm=False)
|
|
||||||
|
|
||||||
try:
|
|
||||||
dst_vm.clone_attrs(src_vm)
|
|
||||||
dst_vm.clone_disk_files(src_vm=src_vm, verbose=options.verbose)
|
|
||||||
except (IOError, OSError) as err:
|
|
||||||
print >> sys.stderr, "ERROR: {0}".format(err)
|
|
||||||
qvm_collection.pop(dst_vm.qid)
|
|
||||||
dst_vm.remove_from_disk()
|
|
||||||
exit(1)
|
|
||||||
|
|
||||||
qvm_collection.save()
|
|
||||||
qvm_collection.unlock_db()
|
|
||||||
|
|
||||||
main()
|
|
@ -1,72 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
if [ $# != 1 -a $# != 2 -o $1 == "--help" -o $1 == "-h" ] ; then
|
|
||||||
echo 'Usage: qvm-create-default-dvm templatename|--default-template|--used-template [script-name|--default-script]'
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
export ROOT=/var/lib/qubes/dvmdata/savefile-root
|
|
||||||
TEMPLATENAME=$1
|
|
||||||
if [ "$TEMPLATENAME" = --used-template ] ; then
|
|
||||||
if [ -e $ROOT ] ; then
|
|
||||||
TEMPLATENAME=$(readlink $ROOT | sed -e 's/.root.img//' -e 's/.*\///')
|
|
||||||
else
|
|
||||||
TEMPLATENAME=--default-template
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
if [ "$TEMPLATENAME" = --default-template ] ; then
|
|
||||||
TEMPLATENAME=$(qubes-prefs --get default-template)
|
|
||||||
if [ "X"$TEMPLATENAME = "X" ] ; then
|
|
||||||
echo No default template ?
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -z "$2" -o "X""$2" = "X""--default-script" ] ; then
|
|
||||||
SCRIPTNAME="vm-default"
|
|
||||||
else
|
|
||||||
SCRIPTNAME=$2
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! [ -d "/var/lib/qubes/vm-templates/$TEMPLATENAME" ] ; then
|
|
||||||
echo /var/lib/qubes/vm-templates/$TEMPLATENAME is not a directory
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
DVMTMPL="$TEMPLATENAME"-dvm
|
|
||||||
DVMTMPLDIR="/var/lib/qubes/appvms/$DVMTMPL"
|
|
||||||
if ! [ -d "$DVMTMPLDIR" ] ; then
|
|
||||||
# unfortunately, currently there are reliability issues with save of a domain
|
|
||||||
# with multiple CPUs and/or more than 4000M RAM
|
|
||||||
if ! qvm-create --force-root --vcpus=1 --internal -t "$TEMPLATENAME" -l gray "$DVMTMPL" ; then exit 1 ; fi
|
|
||||||
MAXMEM=`qvm-prefs --force-root $DVMTMPL|grep ^maxmem|awk '{print $3}'`
|
|
||||||
if [ "$MAXMEM" -ge 4000 ]; then
|
|
||||||
qvm-prefs --force-root -s $DVMTMPL maxmem 4000
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
if ! /usr/lib/qubes/qubes-prepare-saved-domain.sh \
|
|
||||||
"$DVMTMPL" "/var/lib/qubes/appvms/$DVMTMPL/dvm-savefile" $SCRIPTNAME ; then
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
DEFAULT=/var/lib/qubes/dvmdata/default-savefile
|
|
||||||
CURRENT=/var/run/qubes/current-savefile
|
|
||||||
SHMDIR=/dev/shm/qubes
|
|
||||||
SHMCOPY=$SHMDIR/current-savefile
|
|
||||||
rm -f $ROOT $DEFAULT $CURRENT
|
|
||||||
ln -s "/var/lib/qubes/appvms/$DVMTMPL/dvm-savefile" $DEFAULT
|
|
||||||
ln -s "/var/lib/qubes/vm-templates/$TEMPLATENAME/root.img" $ROOT
|
|
||||||
if [ -f /var/lib/qubes/dvmdata/dont-use-shm ] ; then
|
|
||||||
ln -s $DEFAULT $CURRENT
|
|
||||||
else
|
|
||||||
mkdir -m 770 $SHMDIR 2>/dev/null
|
|
||||||
chgrp qubes $SHMDIR 2>/dev/null
|
|
||||||
rm -f $SHMCOPY
|
|
||||||
cp $DEFAULT $SHMCOPY || exit 1
|
|
||||||
chgrp qubes $SHMCOPY
|
|
||||||
chmod 660 $SHMCOPY
|
|
||||||
ln -s $SHMCOPY $CURRENT
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ $(whoami) = "root" ] ; then
|
|
||||||
chgrp qubes "$DVMTMPLDIR" "$DVMTMPLDIR"/*
|
|
||||||
chmod 660 "$DVMTMPLDIR"/*
|
|
||||||
chmod 770 "$DVMTMPLDIR"
|
|
||||||
fi
|
|
||||||
|
|
@ -1,321 +0,0 @@
|
|||||||
#!/usr/bin/python2
|
|
||||||
# -*- encoding: utf8 -*-
|
|
||||||
#
|
|
||||||
# The Qubes OS Project, http://www.qubes-os.org
|
|
||||||
#
|
|
||||||
# Copyright (C) 2012 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.
|
|
||||||
#
|
|
||||||
#
|
|
||||||
import datetime
|
|
||||||
|
|
||||||
from qubes.qubes import QubesVmCollection
|
|
||||||
from optparse import OptionParser;
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
import re
|
|
||||||
import os
|
|
||||||
import socket
|
|
||||||
|
|
||||||
def parse_rule(args):
|
|
||||||
if len(args) < 2:
|
|
||||||
print >>sys.stderr, "ERROR: Rule must have at least address and protocol"
|
|
||||||
return None
|
|
||||||
|
|
||||||
address = args[0]
|
|
||||||
netmask = 32
|
|
||||||
proto = args[1]
|
|
||||||
port = args[2] if len(args) > 2 else None
|
|
||||||
port_end = None
|
|
||||||
|
|
||||||
unmask = address.split("/", 1)
|
|
||||||
if len(unmask) == 2:
|
|
||||||
address = unmask[0]
|
|
||||||
netmask = unmask[1]
|
|
||||||
if netmask.isdigit():
|
|
||||||
if re.match("^([0-9]{1,3}\.){3}[0-9]{1,3}$", address) is None:
|
|
||||||
print >>sys.stderr, "ERROR: Only IP is allowed when specyfying netmask"
|
|
||||||
return None
|
|
||||||
if netmask != "":
|
|
||||||
netmask = int(unmask[1])
|
|
||||||
if netmask < 0 or netmask > 32:
|
|
||||||
print >>sys.stderr, "ERROR: Invalid netmask"
|
|
||||||
return None
|
|
||||||
else:
|
|
||||||
print >>sys.stderr, "ERROR: Invalid netmask"
|
|
||||||
return None
|
|
||||||
|
|
||||||
if address[-1:] == ".":
|
|
||||||
address = address[:-1]
|
|
||||||
|
|
||||||
allowed = re.compile("(?!-)[A-Z\d-]{1,63}(?<!-)$", re.IGNORECASE)
|
|
||||||
if not all(allowed.match(x) for x in address.split(".")):
|
|
||||||
print >>sys.stderr, "ERROR: Invalid hostname"
|
|
||||||
return None
|
|
||||||
|
|
||||||
proto_split = proto.split('/', 1)
|
|
||||||
if len(proto_split) == 2:
|
|
||||||
proto = proto_split[0]
|
|
||||||
port = proto_split[1]
|
|
||||||
|
|
||||||
if proto not in ['tcp', 'udp', 'any']:
|
|
||||||
print >>sys.stderr, "ERROR: Protocol must be one of: 'tcp', 'udp', 'any'"
|
|
||||||
return None
|
|
||||||
|
|
||||||
if proto != "any" and port is None:
|
|
||||||
print >>sys.stderr, "ERROR: Port required for protocol %s" % args[1]
|
|
||||||
return None
|
|
||||||
|
|
||||||
if port is not None:
|
|
||||||
port_range = port.split('-', 1)
|
|
||||||
if len(port_range) == 2:
|
|
||||||
port = port_range[0]
|
|
||||||
port_end = port_range[1]
|
|
||||||
|
|
||||||
if port.isdigit():
|
|
||||||
port = int(port)
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
port = socket.getservbyname(port)
|
|
||||||
except socket.error:
|
|
||||||
print >>sys.stderr, "ERROR: Invalid port/service name '%s'" % port
|
|
||||||
return None
|
|
||||||
|
|
||||||
if port_end is not None and not port_end.isdigit():
|
|
||||||
print >>sys.stderr, "ERROR: Invalid port '%s'" % port_end
|
|
||||||
return None
|
|
||||||
|
|
||||||
if port_end is not None:
|
|
||||||
port_end = int(port_end)
|
|
||||||
|
|
||||||
rule = {}
|
|
||||||
rule['address'] = address
|
|
||||||
rule['netmask'] = netmask
|
|
||||||
rule['proto'] = proto
|
|
||||||
rule['portBegin'] = port
|
|
||||||
rule['portEnd'] = port_end
|
|
||||||
return rule
|
|
||||||
|
|
||||||
def list_rules(rules, numeric=False):
|
|
||||||
fields = [ "num", "address", "proto", "port(s)" ]
|
|
||||||
|
|
||||||
rules_to_display = list()
|
|
||||||
counter = 1
|
|
||||||
for rule in rules:
|
|
||||||
parsed_rule = {
|
|
||||||
'num': "{0:>2}".format(counter),
|
|
||||||
'address': rule['address'] + ('/' + str(rule['netmask']) if rule['netmask'] < 32 else ""),
|
|
||||||
'proto': rule['proto'],
|
|
||||||
'port(s)': '',
|
|
||||||
}
|
|
||||||
if rule['proto'] in ['tcp', 'udp']:
|
|
||||||
parsed_rule['port(s)'] = str(rule['portBegin']) + \
|
|
||||||
('-' + str(rule['portEnd']) if rule['portEnd'] is not None else '')
|
|
||||||
if not numeric and rule['portBegin'] is not None and rule['portEnd'] is None:
|
|
||||||
try:
|
|
||||||
parsed_rule['port(s)'] = str(socket.getservbyport(rule['portBegin']))
|
|
||||||
except socket.error:
|
|
||||||
pass
|
|
||||||
|
|
||||||
if 'expire' in rule:
|
|
||||||
parsed_rule['expire'] = str(datetime.datetime.fromtimestamp(rule[
|
|
||||||
'expire']))
|
|
||||||
|
|
||||||
rules_to_display.append(parsed_rule)
|
|
||||||
counter += 1
|
|
||||||
|
|
||||||
fields_width = {}
|
|
||||||
for f in fields:
|
|
||||||
fields_width[f] = len(f)
|
|
||||||
for r in rules_to_display:
|
|
||||||
if len(r[f]) > fields_width[f]:
|
|
||||||
fields_width[f] = len(r[f])
|
|
||||||
|
|
||||||
# Display the header
|
|
||||||
s = ""
|
|
||||||
for f in fields:
|
|
||||||
fmt="{{0:-^{0}}}-+".format(fields_width[f] + 1)
|
|
||||||
s += fmt.format('-')
|
|
||||||
print s
|
|
||||||
|
|
||||||
s = ""
|
|
||||||
for f in fields:
|
|
||||||
fmt=" {{0:^{0}}} |".format(fields_width[f])
|
|
||||||
s += fmt.format(f)
|
|
||||||
print s
|
|
||||||
|
|
||||||
s = ""
|
|
||||||
for f in fields:
|
|
||||||
fmt="{{0:-^{0}}}-+".format(fields_width[f] + 1)
|
|
||||||
s += fmt.format('-')
|
|
||||||
print s
|
|
||||||
|
|
||||||
# And the content
|
|
||||||
for r in rules_to_display:
|
|
||||||
s = ""
|
|
||||||
for f in fields:
|
|
||||||
fmt=" {{0:<{0}}} |".format(fields_width[f])
|
|
||||||
s += fmt.format(r[f])
|
|
||||||
if 'expire' in r:
|
|
||||||
s += " <-- expires at %s" % r['expire']
|
|
||||||
print s
|
|
||||||
|
|
||||||
def display_firewall(conf, numeric=False):
|
|
||||||
print "Firewall policy: %s" % (
|
|
||||||
"ALLOW all traffic except" if conf['allow'] else "DENY all traffic except")
|
|
||||||
print "ICMP: %s" % ("ALLOW" if conf['allowIcmp'] else 'DENY')
|
|
||||||
print "DNS: %s" % ("ALLOW" if conf['allowDns'] else 'DENY')
|
|
||||||
print "Qubes yum proxy: %s" % ("ALLOW" if conf['allowYumProxy'] else 'DENY')
|
|
||||||
list_rules(conf['rules'], numeric)
|
|
||||||
|
|
||||||
def add_rule(conf, args):
|
|
||||||
rule = parse_rule(args)
|
|
||||||
if rule is None:
|
|
||||||
return False
|
|
||||||
|
|
||||||
conf['rules'].append(rule)
|
|
||||||
return True
|
|
||||||
|
|
||||||
def del_rule(conf, args):
|
|
||||||
if len(args) == 1 and args[0].isdigit():
|
|
||||||
rulenum = int(args[0])
|
|
||||||
if rulenum < 1 or rulenum > len(conf['rules']):
|
|
||||||
print >>sys.stderr, "ERROR: Rule number out of range"
|
|
||||||
return False
|
|
||||||
conf['rules'].pop(rulenum-1)
|
|
||||||
else:
|
|
||||||
rule = parse_rule(args)
|
|
||||||
#print "PARSED: %s" % str(rule)
|
|
||||||
#print "ALL: %s" % str(conf['rules'])
|
|
||||||
if rule is None:
|
|
||||||
return False
|
|
||||||
try:
|
|
||||||
conf['rules'].remove(rule)
|
|
||||||
except ValueError:
|
|
||||||
print >>sys.stderr, "ERROR: Rule not found"
|
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
def allow_deny_value(s):
|
|
||||||
value = None
|
|
||||||
if s == "allow":
|
|
||||||
value = True
|
|
||||||
elif s == "deny":
|
|
||||||
value = False
|
|
||||||
else:
|
|
||||||
print >>sys.stderr, 'ERROR: Only "allow" or "deny" allowed'
|
|
||||||
exit(1)
|
|
||||||
return value
|
|
||||||
|
|
||||||
def main():
|
|
||||||
usage = "usage: %prog [-n] <vm-name> [action] [rule spec]\n"
|
|
||||||
usage += " rule specification can be one of:\n"
|
|
||||||
usage += " address|hostname[/netmask] tcp|udp port[-port]\n"
|
|
||||||
usage += " address|hostname[/netmask] tcp|udp service_name\n"
|
|
||||||
usage += " address|hostname[/netmask] any\n"
|
|
||||||
parser = OptionParser (usage)
|
|
||||||
parser.add_option ("-l", "--list", dest="do_list", action="store_true", default=True,
|
|
||||||
help="List firewall settings (default action)")
|
|
||||||
parser.add_option ("-a", "--add", dest="do_add", action="store_true", default=False,
|
|
||||||
help="Add rule")
|
|
||||||
parser.add_option ("-d", "--del", dest="do_del", action="store_true", default=False,
|
|
||||||
help="Remove rule (given by number or by rule spec)")
|
|
||||||
parser.add_option ("-P", "--policy", dest="set_policy", action="store", default=None,
|
|
||||||
help="Set firewall policy (allow/deny)")
|
|
||||||
parser.add_option ("-i", "--icmp", dest="set_icmp", action="store", default=None,
|
|
||||||
help="Set ICMP access (allow/deny)")
|
|
||||||
parser.add_option ("-D", "--dns", dest="set_dns", action="store", default=None,
|
|
||||||
help="Set DNS access (allow/deny)")
|
|
||||||
parser.add_option ("-Y", "--yum-proxy", dest="set_yum_proxy", action="store", default=None,
|
|
||||||
help="Set access to Qubes yum proxy (allow/deny)")
|
|
||||||
parser.add_option ("-r", "--reload", dest="reload", action="store_true",
|
|
||||||
default=False, help="Reload firewall (implied by any "
|
|
||||||
"change action")
|
|
||||||
|
|
||||||
parser.add_option ("-n", "--numeric", dest="numeric", action="store_true", default=False,
|
|
||||||
help="Display port numbers instead of services (makes sense only with --list)")
|
|
||||||
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 ()
|
|
||||||
if (len (args) < 1):
|
|
||||||
parser.error ("You must specify VM name!")
|
|
||||||
vmname = args[0]
|
|
||||||
args = args[1:]
|
|
||||||
|
|
||||||
if hasattr(os, "geteuid") and os.geteuid() == 0:
|
|
||||||
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, "Retry as unprivileged user."
|
|
||||||
print >> sys.stderr, "... or use --force-root to continue anyway."
|
|
||||||
exit(1)
|
|
||||||
|
|
||||||
if options.do_add or options.do_del or options.set_policy or \
|
|
||||||
options.set_icmp or options.set_dns or options.set_yum_proxy:
|
|
||||||
options.do_list = False
|
|
||||||
qvm_collection = QubesVmCollection()
|
|
||||||
if options.do_list:
|
|
||||||
qvm_collection.lock_db_for_reading()
|
|
||||||
qvm_collection.load()
|
|
||||||
qvm_collection.unlock_db()
|
|
||||||
else:
|
|
||||||
qvm_collection.lock_db_for_writing()
|
|
||||||
qvm_collection.load()
|
|
||||||
|
|
||||||
vm = qvm_collection.get_vm_by_name(vmname)
|
|
||||||
if vm is None:
|
|
||||||
print >> sys.stderr, "A VM with the name '{0}' does not exist in the system.".format(vmname)
|
|
||||||
exit(1)
|
|
||||||
|
|
||||||
changed = False
|
|
||||||
conf = vm.get_firewall_conf()
|
|
||||||
|
|
||||||
if options.set_policy:
|
|
||||||
conf['allow'] = allow_deny_value(options.set_policy)
|
|
||||||
changed = True
|
|
||||||
if options.set_icmp:
|
|
||||||
conf['allowIcmp'] = allow_deny_value(options.set_icmp)
|
|
||||||
changed = True
|
|
||||||
if options.set_dns:
|
|
||||||
conf['allowDns'] = allow_deny_value(options.set_dns)
|
|
||||||
changed = True
|
|
||||||
if options.set_yum_proxy:
|
|
||||||
conf['allowYumProxy'] = allow_deny_value(options.set_yum_proxy)
|
|
||||||
changed = True
|
|
||||||
|
|
||||||
if options.do_add:
|
|
||||||
changed = add_rule(conf, args)
|
|
||||||
elif options.do_del:
|
|
||||||
changed = del_rule(conf, args)
|
|
||||||
elif options.do_list and not options.reload:
|
|
||||||
if not vm.has_firewall():
|
|
||||||
print "INFO: This VM has no firewall rules set, below defaults are listed"
|
|
||||||
display_firewall(conf, options.numeric)
|
|
||||||
|
|
||||||
if changed:
|
|
||||||
vm.write_firewall_conf(conf)
|
|
||||||
qvm_collection.save()
|
|
||||||
if changed or options.reload:
|
|
||||||
if vm.is_running():
|
|
||||||
if vm.netvm is not None and vm.netvm.is_proxyvm():
|
|
||||||
vm.netvm.write_iptables_qubesdb_entry()
|
|
||||||
|
|
||||||
if not options.do_list:
|
|
||||||
qvm_collection.unlock_db()
|
|
||||||
|
|
||||||
|
|
||||||
main()
|
|
@ -1,66 +0,0 @@
|
|||||||
#!/usr/bin/python2
|
|
||||||
# -*- encoding: utf8 -*-
|
|
||||||
#
|
|
||||||
# The Qubes OS Project, http://www.qubes-os.org
|
|
||||||
#
|
|
||||||
# Copyright (C) 2011 Marek Marczykowski <marmarek@mimuw.edu.pl>
|
|
||||||
#
|
|
||||||
# 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 qubes.qubes import QubesVmCollection
|
|
||||||
from qubes.qubes import QubesException
|
|
||||||
from qubes.qubesutils import parse_size
|
|
||||||
from optparse import OptionParser
|
|
||||||
import subprocess
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
|
|
||||||
qvm_run_path = "/usr/bin/qvm-run"
|
|
||||||
|
|
||||||
def main():
|
|
||||||
usage = "usage: %prog <vm-name> <size>"
|
|
||||||
parser = OptionParser (usage)
|
|
||||||
|
|
||||||
(options, args) = parser.parse_args ()
|
|
||||||
if (len (args) != 2):
|
|
||||||
parser.error ("You must specify VM name and new size!")
|
|
||||||
vmname = args[0]
|
|
||||||
size = args[1]
|
|
||||||
|
|
||||||
qvm_collection = QubesVmCollection()
|
|
||||||
qvm_collection.lock_db_for_reading()
|
|
||||||
qvm_collection.load()
|
|
||||||
qvm_collection.unlock_db()
|
|
||||||
|
|
||||||
vm = qvm_collection.get_vm_by_name(vmname)
|
|
||||||
if vm is None:
|
|
||||||
print >> sys.stderr, "A VM with the name '{0}' does not exist in the system.".format(vmname)
|
|
||||||
exit(1)
|
|
||||||
|
|
||||||
size_bytes = parse_size(size)
|
|
||||||
|
|
||||||
try:
|
|
||||||
vm.resize_private_img(size_bytes)
|
|
||||||
except (IOError, OSError, QubesException) as err:
|
|
||||||
print >> sys.stderr, "ERROR: {0}".format(err)
|
|
||||||
exit (1)
|
|
||||||
|
|
||||||
exit (0)
|
|
||||||
|
|
||||||
|
|
||||||
main()
|
|
@ -1,72 +0,0 @@
|
|||||||
#!/usr/bin/python2
|
|
||||||
# -*- encoding: utf8 -*-
|
|
||||||
#
|
|
||||||
# The Qubes OS Project, http://www.qubes-os.org
|
|
||||||
#
|
|
||||||
# Copyright (C) 2014 Marek Marczykowski-Górecki <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 qubes.qubes import QubesVmCollection
|
|
||||||
from qubes.qubes import QubesException
|
|
||||||
from qubes.qubesutils import parse_size
|
|
||||||
from optparse import OptionParser
|
|
||||||
import subprocess
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
|
|
||||||
def main():
|
|
||||||
usage = "usage: %prog <vm-name> <size>"
|
|
||||||
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 ()
|
|
||||||
if (len (args) != 2):
|
|
||||||
parser.error ("You must specify VM name and new size!")
|
|
||||||
vmname = args[0]
|
|
||||||
size = args[1]
|
|
||||||
|
|
||||||
qvm_collection = QubesVmCollection()
|
|
||||||
qvm_collection.lock_db_for_reading()
|
|
||||||
qvm_collection.load()
|
|
||||||
qvm_collection.unlock_db()
|
|
||||||
|
|
||||||
vm = qvm_collection.get_vm_by_name(vmname)
|
|
||||||
if vm is None:
|
|
||||||
print >> sys.stderr, "A VM with the name '{0}' does not exist in the system.".format(vmname)
|
|
||||||
exit(1)
|
|
||||||
|
|
||||||
size_bytes = parse_size(size)
|
|
||||||
|
|
||||||
if not hasattr(vm, 'resize_root_img'):
|
|
||||||
print >> sys.stderr, "Operation not supported for this VM type"
|
|
||||||
exit(1)
|
|
||||||
|
|
||||||
try:
|
|
||||||
vm.resize_root_img(size_bytes, allow_start=options.allow_start)
|
|
||||||
except (IOError, OSError, QubesException) as err:
|
|
||||||
print >> sys.stderr, "ERROR: {0}".format(err)
|
|
||||||
exit (1)
|
|
||||||
|
|
||||||
exit (0)
|
|
||||||
|
|
||||||
|
|
||||||
main()
|
|
@ -1,35 +0,0 @@
|
|||||||
#!/usr/bin/python2
|
|
||||||
# -*- encoding: utf8 -*-
|
|
||||||
#
|
|
||||||
# The Qubes OS Project, http://www.qubes-os.org
|
|
||||||
#
|
|
||||||
# Copyright (C) 2010 Joanna Rutkowska <joanna@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 qubes.qubes import QubesVmCollection,vmm
|
|
||||||
import sys
|
|
||||||
|
|
||||||
def main():
|
|
||||||
vmm.offline_mode = True
|
|
||||||
qvm_collection = QubesVmCollection()
|
|
||||||
if qvm_collection.check_if_storage_exists():
|
|
||||||
print >> sys.stderr, "Storage exists, not overwriting."
|
|
||||||
exit(1)
|
|
||||||
qvm_collection.create_empty_storage()
|
|
||||||
|
|
||||||
main()
|
|
@ -1,141 +0,0 @@
|
|||||||
#!/usr/bin/python2
|
|
||||||
# -*- encoding: utf8 -*-
|
|
||||||
#
|
|
||||||
# The Qubes OS Project, http://www.qubes-os.org
|
|
||||||
#
|
|
||||||
# Copyright (C) 2010 Marek Marczykowski <marmarek@mimuw.edu.pl>
|
|
||||||
#
|
|
||||||
# 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 qubes.qubes import QubesVmCollection
|
|
||||||
from optparse import OptionParser
|
|
||||||
import subprocess
|
|
||||||
import os
|
|
||||||
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():
|
|
||||||
usage = "usage: %prog -l [options] <vm-name>\n"\
|
|
||||||
"usage: %prog -a [options] <vm-name> <device>\n"\
|
|
||||||
"usage: %prog -d [options] <vm-name> <device>\n"\
|
|
||||||
"List/set VM PCI devices."
|
|
||||||
|
|
||||||
parser = OptionParser (usage)
|
|
||||||
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 ("-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 ()
|
|
||||||
if (len (args) < 1):
|
|
||||||
parser.error ("You must provide at least the vmname!")
|
|
||||||
|
|
||||||
vmname = args[0]
|
|
||||||
|
|
||||||
if options.do_list + options.do_add + options.do_delete + \
|
|
||||||
options.do_add_class > 1:
|
|
||||||
print >> sys.stderr, "Only one of -l -a -d -C is allowed!"
|
|
||||||
exit(1)
|
|
||||||
|
|
||||||
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.lock_db_for_writing()
|
|
||||||
qvm_collection.load()
|
|
||||||
else:
|
|
||||||
qvm_collection = QubesVmCollection()
|
|
||||||
qvm_collection.lock_db_for_reading()
|
|
||||||
qvm_collection.load()
|
|
||||||
qvm_collection.unlock_db()
|
|
||||||
|
|
||||||
vm = qvm_collection.get_vm_by_name(vmname)
|
|
||||||
if vm is None or vm.qid not in qvm_collection:
|
|
||||||
print >> sys.stderr, "A VM with the name '{0}' does not exist in the system.".format(vmname)
|
|
||||||
exit(1)
|
|
||||||
|
|
||||||
if options.do_add:
|
|
||||||
if len (args) < 2:
|
|
||||||
print >> sys.stderr, "You must specify the PCI device to add"
|
|
||||||
exit (1)
|
|
||||||
|
|
||||||
pci = args[1]
|
|
||||||
vm.pci_add(pci)
|
|
||||||
qvm_collection.save()
|
|
||||||
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:
|
|
||||||
if len (args) < 2:
|
|
||||||
print >> sys.stderr, "You must specify the PCI device to delete"
|
|
||||||
exit (1)
|
|
||||||
|
|
||||||
pci = args[1]
|
|
||||||
vm.pci_remove(pci)
|
|
||||||
qvm_collection.save()
|
|
||||||
qvm_collection.unlock_db()
|
|
||||||
|
|
||||||
else:
|
|
||||||
# do_list
|
|
||||||
print str(vm.pcidevs)
|
|
||||||
|
|
||||||
main()
|
|
@ -1,142 +0,0 @@
|
|||||||
#!/usr/bin/python2
|
|
||||||
# -*- encoding: utf8 -*-
|
|
||||||
#
|
|
||||||
# The Qubes OS Project, http://www.qubes-os.org
|
|
||||||
#
|
|
||||||
# Copyright (C) 2011 Marek Marczykowski <marmarek@mimuw.edu.pl>
|
|
||||||
#
|
|
||||||
# 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 qubes.qubes import QubesVmCollection
|
|
||||||
from qubes.qubes import QubesException
|
|
||||||
from optparse import OptionParser
|
|
||||||
import subprocess
|
|
||||||
import os
|
|
||||||
import time
|
|
||||||
import glob
|
|
||||||
import sys
|
|
||||||
|
|
||||||
def main():
|
|
||||||
usage = "usage: %prog [options] <template-name>"
|
|
||||||
parser = OptionParser (usage)
|
|
||||||
parser.add_option ("--force", action="store_true", dest="force", default=False,
|
|
||||||
help="Do not prompt for confirmation")
|
|
||||||
|
|
||||||
(options, args) = parser.parse_args ()
|
|
||||||
if (len (args) != 1):
|
|
||||||
parser.error ("You must specify TemplateVM name!")
|
|
||||||
vmname = args[0]
|
|
||||||
|
|
||||||
if hasattr(os, "geteuid") and os.geteuid() != 0:
|
|
||||||
print >> sys.stderr, "ERROR: This tool must be run as root!"
|
|
||||||
exit(1)
|
|
||||||
|
|
||||||
qvm_collection = QubesVmCollection()
|
|
||||||
qvm_collection.lock_db_for_reading()
|
|
||||||
qvm_collection.load()
|
|
||||||
qvm_collection.unlock_db()
|
|
||||||
|
|
||||||
vm = qvm_collection.get_vm_by_name(vmname)
|
|
||||||
if vm is None:
|
|
||||||
print >> sys.stderr, "A VM with the name '{0}' does not exist in the system.".format(vmname)
|
|
||||||
exit(1)
|
|
||||||
|
|
||||||
if not vm.is_template():
|
|
||||||
print >> sys.stderr, "A VM '{0}' is not template.".format(vmname)
|
|
||||||
exit(1)
|
|
||||||
|
|
||||||
if vm.is_running():
|
|
||||||
print >> sys.stderr, "You must stop VM first."
|
|
||||||
exit(1)
|
|
||||||
|
|
||||||
oldcow_img = vm.rootcow_img + '.old'
|
|
||||||
oldcow_stat = os.stat(oldcow_img)
|
|
||||||
oldcow_time_str = time.strftime("%F %T", time.gmtime(oldcow_stat.st_mtime))
|
|
||||||
|
|
||||||
root_stat = os.stat(vm.root_img)
|
|
||||||
old_dmdev = "/dev/mapper/snapshot-{0:x}:{1}-{2:x}:{3}".format(
|
|
||||||
root_stat[2], root_stat[1],
|
|
||||||
oldcow_stat[2], oldcow_stat[1])
|
|
||||||
|
|
||||||
snapshots = glob.glob('/dev/mapper/snapshot-{0:x}:{1}-*'.format(root_stat[2], root_stat[1]))
|
|
||||||
snapshot_present = False
|
|
||||||
for dev in snapshots:
|
|
||||||
if dev == old_dmdev:
|
|
||||||
snapshot_present = True
|
|
||||||
else:
|
|
||||||
print >> sys.stderr, "ERROR: You must shutdown all VMs running system older/newer than last good one."
|
|
||||||
exit(1)
|
|
||||||
|
|
||||||
root_blocks = os.path.getsize(vm.root_img)/512
|
|
||||||
if not snapshot_present:
|
|
||||||
p = subprocess.Popen (["/etc/xen/scripts/block-snapshot", "prepare",
|
|
||||||
"snapshot", "{0}:{1}".format(vm.root_img, oldcow_img)],
|
|
||||||
stdout=subprocess.PIPE)
|
|
||||||
result = p.communicate()
|
|
||||||
if result[0].strip() != old_dmdev:
|
|
||||||
print >> sys.stderr, "ERROR: Cannot create snapshot device ({0} != {1})".format(
|
|
||||||
result[0].strip(), old_dmdev)
|
|
||||||
exit(1)
|
|
||||||
|
|
||||||
|
|
||||||
print "INFO: Reverting template changes done at {0}".format(oldcow_time_str)
|
|
||||||
if not options.force:
|
|
||||||
prompt = raw_input ("Do you want to proceed? [y/N] ")
|
|
||||||
if not (prompt == "y" or prompt == "Y"):
|
|
||||||
exit (0)
|
|
||||||
|
|
||||||
p = subprocess.Popen(["/sbin/dmsetup", "table", old_dmdev], stdout=subprocess.PIPE)
|
|
||||||
result = p.communicate()
|
|
||||||
dm_table = result[0]
|
|
||||||
dm_table_elements = dm_table.split(' ')
|
|
||||||
if dm_table_elements[2] != 'snapshot':
|
|
||||||
print >> sys.stderr, "ERROR: Unexpected device-mapper type ({0}). Template changes reverting already running".format(dm_table_elements[2])
|
|
||||||
exit(1)
|
|
||||||
|
|
||||||
dm_table_elements[2] = 'snapshot-merge'
|
|
||||||
dm_table = ' '.join(dm_table_elements)
|
|
||||||
subprocess.check_call(["/sbin/dmsetup", "reload", old_dmdev, "--table", dm_table])
|
|
||||||
# Reload new table into LIVE slot
|
|
||||||
subprocess.check_call(["/sbin/dmsetup", "suspend", old_dmdev])
|
|
||||||
subprocess.check_call(["/sbin/dmsetup", "resume", old_dmdev])
|
|
||||||
# Wait to snapshot merge completed
|
|
||||||
while True:
|
|
||||||
p = subprocess.Popen(["/sbin/dmsetup", "status", old_dmdev], stdout=subprocess.PIPE)
|
|
||||||
result = p.communicate()
|
|
||||||
status_details = result[0].split(' ')
|
|
||||||
blocks_used = status_details[3].split('/')[0]
|
|
||||||
if int(blocks_used) == int(status_details[4]):
|
|
||||||
break
|
|
||||||
print "\r-> Reverting template changes: {0} of {1} left".format(blocks_used, root_blocks),
|
|
||||||
time.sleep(1)
|
|
||||||
print "\r-> Reverting template changes: done ".format(blocks_used, root_blocks)
|
|
||||||
|
|
||||||
dm_table_elements[2] = 'snapshot'
|
|
||||||
dm_table = ' '.join(dm_table_elements)
|
|
||||||
subprocess.check_call(["/sbin/dmsetup", "reload", old_dmdev, "--table", dm_table])
|
|
||||||
# Reload new table into LIVE slot
|
|
||||||
subprocess.check_call(["/sbin/dmsetup", "suspend", old_dmdev])
|
|
||||||
subprocess.check_call(["/sbin/dmsetup", "resume", old_dmdev])
|
|
||||||
|
|
||||||
subprocess.check_call(["/etc/xen/scripts/block-snapshot", "cleanup",
|
|
||||||
"snapshot", old_dmdev])
|
|
||||||
|
|
||||||
os.rename(oldcow_img, vm.rootcow_img)
|
|
||||||
exit(0)
|
|
||||||
|
|
||||||
|
|
||||||
main()
|
|
@ -1,98 +0,0 @@
|
|||||||
#!/usr/bin/python
|
|
||||||
# -*- encoding: utf8 -*-
|
|
||||||
#
|
|
||||||
# The Qubes OS Project, http://www.qubes-os.org
|
|
||||||
#
|
|
||||||
# Copyright (C) 2012 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 qubes.qubes import QubesVmCollection
|
|
||||||
from optparse import OptionParser;
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
import re
|
|
||||||
|
|
||||||
def do_list(vm):
|
|
||||||
max_len = 0
|
|
||||||
for s in vm.services.keys():
|
|
||||||
max_len = max(max_len, len(s))
|
|
||||||
fmt="{{0:<{0}}}: {{1}}".format(max_len)
|
|
||||||
|
|
||||||
for s in vm.services.keys():
|
|
||||||
print fmt.format (s, "Enabled" if vm.services[s] else "Disabled")
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
usage = "usage: %prog <vm-name> [action] [service]\n"
|
|
||||||
parser = OptionParser (usage)
|
|
||||||
parser.add_option ("-l", "--list", dest="do_list", action="store_true", default=True,
|
|
||||||
help="List services (default action)")
|
|
||||||
parser.add_option ("-e", "--enable", dest="set_enable", action="store_true", default=False,
|
|
||||||
help="Enable service")
|
|
||||||
parser.add_option ("-d", "--disable", dest="set_disable", action="store_true", default=False,
|
|
||||||
help="Disable service")
|
|
||||||
parser.add_option ("-D", "--default", dest="set_default", action="store_true", default=False,
|
|
||||||
help="Reset service to its default state (remove from the list)")
|
|
||||||
|
|
||||||
(options, args) = parser.parse_args ()
|
|
||||||
if (len (args) < 1):
|
|
||||||
parser.error ("You must specify VM name!")
|
|
||||||
vmname = args[0]
|
|
||||||
args = args[1:]
|
|
||||||
|
|
||||||
if options.set_enable or options.set_disable or options.set_default:
|
|
||||||
if (len(args) < 1):
|
|
||||||
parser.error("You must specify service name!")
|
|
||||||
options.do_list = False
|
|
||||||
|
|
||||||
qvm_collection = QubesVmCollection()
|
|
||||||
if options.do_list:
|
|
||||||
qvm_collection.lock_db_for_reading()
|
|
||||||
qvm_collection.load()
|
|
||||||
qvm_collection.unlock_db()
|
|
||||||
else:
|
|
||||||
qvm_collection.lock_db_for_writing()
|
|
||||||
qvm_collection.load()
|
|
||||||
|
|
||||||
vm = qvm_collection.get_vm_by_name(vmname)
|
|
||||||
if vm is None:
|
|
||||||
print >> sys.stderr, "A VM with the name '{0}' does not exist in the system.".format(vmname)
|
|
||||||
exit(1)
|
|
||||||
|
|
||||||
changed = False
|
|
||||||
if options.do_list:
|
|
||||||
do_list(vm)
|
|
||||||
elif options.set_enable:
|
|
||||||
vm.services[args[0]] = True
|
|
||||||
changed = True
|
|
||||||
elif options.set_disable:
|
|
||||||
vm.services[args[0]] = False
|
|
||||||
changed = True
|
|
||||||
elif options.set_default:
|
|
||||||
if vm.services.has_key(args[0]):
|
|
||||||
vm.services.pop(args[0])
|
|
||||||
changed = True
|
|
||||||
|
|
||||||
if changed:
|
|
||||||
qvm_collection.save()
|
|
||||||
|
|
||||||
if not options.do_list:
|
|
||||||
qvm_collection.unlock_db()
|
|
||||||
|
|
||||||
|
|
||||||
main()
|
|
@ -1,73 +0,0 @@
|
|||||||
#!/usr/bin/python2
|
|
||||||
# -*- encoding: utf8 -*-
|
|
||||||
#
|
|
||||||
# The Qubes OS Project, http://www.qubes-os.org
|
|
||||||
#
|
|
||||||
# Copyright (C) 2011 Marek Marczykowski <marmarek@mimuw.edu.pl>
|
|
||||||
#
|
|
||||||
# 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 qubes.qubes import QubesVmCollection,vmm
|
|
||||||
from qubes.qubes import QubesException
|
|
||||||
from optparse import OptionParser
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
|
|
||||||
def main():
|
|
||||||
usage = "usage: %prog [options] <vm-name>"
|
|
||||||
parser = OptionParser (usage)
|
|
||||||
parser.add_option ("--offline-mode", dest="offline_mode",
|
|
||||||
action="store_true", default=False,
|
|
||||||
help="Offline mode")
|
|
||||||
|
|
||||||
(options, args) = parser.parse_args ()
|
|
||||||
if (len (args) != 1):
|
|
||||||
parser.error ("You must specify VM name!")
|
|
||||||
vmname = args[0]
|
|
||||||
|
|
||||||
if options.offline_mode:
|
|
||||||
vmm.offline_mode = True
|
|
||||||
|
|
||||||
qvm_collection = QubesVmCollection()
|
|
||||||
qvm_collection.lock_db_for_reading()
|
|
||||||
qvm_collection.load()
|
|
||||||
qvm_collection.unlock_db()
|
|
||||||
|
|
||||||
vm = qvm_collection.get_vm_by_name(vmname)
|
|
||||||
if vm is None:
|
|
||||||
print >> sys.stderr, "A VM with the name '{0}' does not exist in the system.".format(vmname)
|
|
||||||
exit(1)
|
|
||||||
|
|
||||||
if not vm.is_template():
|
|
||||||
print >> sys.stderr, "A VM '{0}' is not template.".format(vmname)
|
|
||||||
exit(1)
|
|
||||||
|
|
||||||
if not vmm.offline_mode and vm.is_running():
|
|
||||||
print >> sys.stderr, "You must stop VM first."
|
|
||||||
exit(1)
|
|
||||||
|
|
||||||
try:
|
|
||||||
vm.verify_files()
|
|
||||||
vm.commit_changes()
|
|
||||||
except (IOError, OSError, QubesException) as err:
|
|
||||||
print >> sys.stderr, "ERROR: {0}".format(err)
|
|
||||||
exit (1)
|
|
||||||
|
|
||||||
exit (0)
|
|
||||||
|
|
||||||
|
|
||||||
main()
|
|
@ -1,171 +0,0 @@
|
|||||||
#!/usr/bin/python2
|
|
||||||
# -*- encoding: utf8 -*-
|
|
||||||
#
|
|
||||||
# The Qubes OS Project, http://www.qubes-os.org
|
|
||||||
#
|
|
||||||
# Copyright (C) 2014 Matt McCutchen <matt@mattmccutchen.net>
|
|
||||||
# Copyright (C) 2015 Marek Marczykowski-Górecki <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.
|
|
||||||
#
|
|
||||||
#
|
|
||||||
|
|
||||||
import os
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
import time
|
|
||||||
from qubes import qubes
|
|
||||||
from qubes import qubesutils
|
|
||||||
|
|
||||||
|
|
||||||
def is_dvm_up_to_date(tmpl, dvm_tmpl):
|
|
||||||
dvm_savefile_path = os.path.join(dvm_tmpl.dir_path, "dvm-savefile")
|
|
||||||
if not os.path.isfile(dvm_savefile_path):
|
|
||||||
return False
|
|
||||||
|
|
||||||
dvm_mtime = os.path.getmtime(dvm_savefile_path)
|
|
||||||
root_mtime = os.path.getmtime(tmpl.root_img)
|
|
||||||
if dvm_mtime < root_mtime:
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
if len(sys.argv) != 2:
|
|
||||||
print >> sys.stderr, 'Usage: qvm-trim-template TEMPLATEVM_NAME'
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
tvm_name = sys.argv[1]
|
|
||||||
|
|
||||||
qvm_collection = qubes.QubesVmCollection()
|
|
||||||
qvm_collection.lock_db_for_writing()
|
|
||||||
qvm_collection.load()
|
|
||||||
|
|
||||||
tvm = qvm_collection.get_vm_by_name(tvm_name)
|
|
||||||
if tvm is None:
|
|
||||||
print >> sys.stderr, 'VM \'{}\' does not exists'.format(tvm_name)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
if not tvm.is_template():
|
|
||||||
print >> sys.stderr, '{} is not template'.format(tvm_name)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
if tvm.is_running():
|
|
||||||
print >> sys.stderr, 'Please stop the TemplateVM first.'
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
outdated_children = [c for c in qvm_collection.get_vms_based_on(tvm.qid) if
|
|
||||||
c.is_outdated()]
|
|
||||||
if outdated_children:
|
|
||||||
print >> sys.stderr, 'Please stop (or restart) the following outdated VMs based on the template first:\n%s' % ', '.join(
|
|
||||||
|
|
||||||
c.name for c in outdated_children)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
rootcow_old = tvm.rootcow_img + '.old'
|
|
||||||
print 'Disk usage before:'
|
|
||||||
subprocess.check_call(['du', tvm.root_img] + (
|
|
||||||
['--total', rootcow_old] if os.path.exists(rootcow_old) else []))
|
|
||||||
|
|
||||||
# root-cow.img.old is likely to be invalid once we trim root.img, so go ahead and delete it.
|
|
||||||
# (Note, root-cow.img should be logically empty because the TemplateVM is not running.)
|
|
||||||
if os.path.exists(rootcow_old):
|
|
||||||
os.remove(rootcow_old)
|
|
||||||
|
|
||||||
dvm_tmpl = qvm_collection.get_vm_by_name(tvm.name + '-dvm')
|
|
||||||
if dvm_tmpl is None:
|
|
||||||
touch_dvm_savefile = False
|
|
||||||
else:
|
|
||||||
touch_dvm_savefile = is_dvm_up_to_date(tvm, dvm_tmpl)
|
|
||||||
|
|
||||||
print >> sys.stderr, "Creating temporary VM..."
|
|
||||||
trim_vmname = "trim-{}".format(tvm_name[:31 - len('trim-')])
|
|
||||||
fstrim_vm = qvm_collection.get_vm_by_name(trim_vmname)
|
|
||||||
if fstrim_vm is not None:
|
|
||||||
if not fstrim_vm.internal:
|
|
||||||
print >>sys.stderr, \
|
|
||||||
"ERROR: VM '{}' already exists and is not marked as internal. " \
|
|
||||||
"Remove it manually."
|
|
||||||
fstrim_vm.remove_from_disk()
|
|
||||||
qvm_collection.pop(fstrim_vm.qid)
|
|
||||||
fstrim_vm = qvm_collection.add_new_vm(
|
|
||||||
"QubesAppVm",
|
|
||||||
template=tvm,
|
|
||||||
name=trim_vmname,
|
|
||||||
netvm=None,
|
|
||||||
internal=True,
|
|
||||||
)
|
|
||||||
if not fstrim_vm:
|
|
||||||
print >> sys.stderr, "ERROR: Failed to create new VM"
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
fstrim_vm.create_on_disk()
|
|
||||||
fstrim_vm.start(start_guid=False, verbose=True)
|
|
||||||
|
|
||||||
print >> sys.stderr, "Performing fstrim now..."
|
|
||||||
fstrim_process = fstrim_vm.run("/bin/sh", user="root", passio_popen=True,
|
|
||||||
gui=False)
|
|
||||||
fstrim_process.stdin.write('''
|
|
||||||
until [ -r /dev/xvdi ]; do
|
|
||||||
sleep 1
|
|
||||||
done
|
|
||||||
mkdir /tmp/root
|
|
||||||
mount -o ro /dev/xvdi /tmp/root
|
|
||||||
fstrim -v /tmp/root
|
|
||||||
poweroff
|
|
||||||
''')
|
|
||||||
fstrim_process.stdin.close()
|
|
||||||
|
|
||||||
qubesutils.block_attach(qvm_collection, fstrim_vm,
|
|
||||||
{
|
|
||||||
'vm': 'dom0',
|
|
||||||
'device': tvm.root_img,
|
|
||||||
'mode': 'w',
|
|
||||||
},
|
|
||||||
mode='w',
|
|
||||||
frontend='xvdi')
|
|
||||||
|
|
||||||
# At this point, the trim should run and the vm should shut down by itself and
|
|
||||||
# detach the block device.
|
|
||||||
|
|
||||||
fstrim_process.wait()
|
|
||||||
print >> sys.stderr, "fstrim done, cleaning up..."
|
|
||||||
|
|
||||||
while fstrim_vm.is_running():
|
|
||||||
time.sleep(1)
|
|
||||||
|
|
||||||
fstrim_vm.remove_from_disk()
|
|
||||||
qvm_collection.pop(fstrim_vm.qid)
|
|
||||||
|
|
||||||
# if DispVM template was up to date, keep that state
|
|
||||||
if touch_dvm_savefile:
|
|
||||||
dvm_savefile_path = os.path.join(dvm_tmpl.dir_path, 'dvm-savefile')
|
|
||||||
os.utime(dvm_savefile_path, None)
|
|
||||||
# If this is default DispVM, make sure that tmpfs copy of the file
|
|
||||||
# (if enabled) also has mtime updated
|
|
||||||
if os.stat('/var/lib/qubes/dvmdata/default-savefile').st_ino == \
|
|
||||||
os.stat(dvm_savefile_path).st_ino:
|
|
||||||
os.utime('/var/run/qubes/current-savefile', None)
|
|
||||||
|
|
||||||
# do not save, all changes to qubes.xml should be reversed
|
|
||||||
qvm_collection.unlock_db()
|
|
||||||
|
|
||||||
print 'Disk usage after:'
|
|
||||||
subprocess.check_call(['du', tvm.root_img])
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
@ -1,141 +0,0 @@
|
|||||||
#!/usr/bin/python2
|
|
||||||
# -*- encoding: utf8 -*-
|
|
||||||
#
|
|
||||||
# The Qubes OS Project, http://www.qubes-os.org
|
|
||||||
#
|
|
||||||
# Copyright (C) 2010 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 qubes.qubes import QubesVmCollection, QubesException
|
|
||||||
from qubes.qubesutils import usb_list,usb_attach,usb_detach,usb_detach_all,usb_check_attached
|
|
||||||
from optparse import OptionParser
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
|
|
||||||
def main():
|
|
||||||
usage = "usage: %prog -l [options]\n"\
|
|
||||||
"usage: %prog -a [options] <vm-name> <device-vm-name>:<device>\n"\
|
|
||||||
"usage: %prog -d [options] <device-vm-name>:<device>\n"\
|
|
||||||
"List/set VM USB devices."
|
|
||||||
# "usage: %prog -d [options] <vm-name>\n"\
|
|
||||||
|
|
||||||
parser = OptionParser (usage)
|
|
||||||
parser.add_option ("-l", "--list", action="store_true", dest="do_list", default=False)
|
|
||||||
parser.add_option ("-a", "--attach", action="store_true", dest="do_attach", default=False)
|
|
||||||
parser.add_option ("-d", "--detach", action="store_true", dest="do_detach", default=False)
|
|
||||||
# parser.add_option ("-f", "--frontend", dest="frontend",
|
|
||||||
# help="Specify device id at destination VM [default: first unused]")
|
|
||||||
parser.add_option ("--no-auto-detach", dest="auto_detach", action="store_false", default=True,
|
|
||||||
help="Fail when device already connected to other VM")
|
|
||||||
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 ()
|
|
||||||
|
|
||||||
if hasattr(os, "geteuid") and os.geteuid() == 0:
|
|
||||||
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, "Retry as unprivileged user."
|
|
||||||
print >> sys.stderr, "... or use --force-root to continue anyway."
|
|
||||||
exit(1)
|
|
||||||
|
|
||||||
if options.do_list + options.do_attach + options.do_detach > 1:
|
|
||||||
print >> sys.stderr, "Only one of -l -a -d is allowed!"
|
|
||||||
exit (1)
|
|
||||||
|
|
||||||
qvm_collection = QubesVmCollection()
|
|
||||||
qvm_collection.lock_db_for_reading()
|
|
||||||
qvm_collection.load()
|
|
||||||
qvm_collection.unlock_db()
|
|
||||||
|
|
||||||
if options.do_attach:
|
|
||||||
if (len (args) != 2):
|
|
||||||
parser.error ("You must provide vm name and device!")
|
|
||||||
vm = qvm_collection.get_vm_by_name(args[0])
|
|
||||||
if vm is None:
|
|
||||||
parser.error ("Invalid VM name: %s" % args[0])
|
|
||||||
|
|
||||||
# FIXME: here we assume that device is always in form "domain:dev",
|
|
||||||
# which can be changed in the future
|
|
||||||
if args[1].find(":") < 0:
|
|
||||||
parser.error("Invalid device syntax: %s" % args[1])
|
|
||||||
backend_vm = qvm_collection.get_vm_by_name(args[1].split(":")[0])
|
|
||||||
if backend_vm is None:
|
|
||||||
parser.error("No such VM: {}".format(args[1].split(":")[0]))
|
|
||||||
dev_list = usb_list(qvm_collection, vm=backend_vm)
|
|
||||||
if not args[1] in dev_list.keys():
|
|
||||||
parser.error("Invalid device name: %s" % args[1])
|
|
||||||
dev = dev_list[args[1]]
|
|
||||||
assert backend_vm is not None
|
|
||||||
|
|
||||||
kwargs = {}
|
|
||||||
# if options.frontend:
|
|
||||||
# kwargs['frontend'] = options.frontend
|
|
||||||
kwargs['auto_detach'] = options.auto_detach
|
|
||||||
try:
|
|
||||||
usb_attach(qvm_collection, vm, dev, **kwargs)
|
|
||||||
except QubesException as e:
|
|
||||||
print >> sys.stderr, "ERROR: %s" % str(e)
|
|
||||||
sys.exit(1)
|
|
||||||
elif options.do_detach:
|
|
||||||
if (len (args) < 1):
|
|
||||||
parser.error ("You must provide device or vm name!")
|
|
||||||
if len(args) > 1:
|
|
||||||
parser.error ("Too many parameters")
|
|
||||||
# Check if provided name is VM
|
|
||||||
vm = qvm_collection.get_vm_by_name(args[0])
|
|
||||||
if vm is not None:
|
|
||||||
#kwargs = {}
|
|
||||||
#if options.frontend:
|
|
||||||
# kwargs['frontend'] = options.frontend
|
|
||||||
# usb_detach(vm, **kwargs)
|
|
||||||
#else:
|
|
||||||
usb_detach_all(qvm_collection, vm)
|
|
||||||
else:
|
|
||||||
# Maybe usbvm:device?
|
|
||||||
|
|
||||||
# FIXME: nasty copy-paste from attach code half a page above
|
|
||||||
# FIXME: here we assume that device is always in form "domain:dev",
|
|
||||||
# which can be changed in the future
|
|
||||||
if args[0].find(":") < 0:
|
|
||||||
parser.error("Invalid device syntax: %s" % args[0])
|
|
||||||
backend_vm = qvm_collection.get_vm_by_name(args[0].split(":")[0])
|
|
||||||
if backend_vm is None:
|
|
||||||
parser.error("No such VM: {}".format(args[0].split(":")[0]))
|
|
||||||
dev_list = usb_list(qvm_collection, vm=backend_vm)
|
|
||||||
if not args[0] in dev_list.keys():
|
|
||||||
parser.error("Invalid device name: %s" % args[0])
|
|
||||||
dev = dev_list[args[0]]
|
|
||||||
attached_to = usb_check_attached(qvm_collection, dev)
|
|
||||||
if attached_to is None:
|
|
||||||
print >> sys.stderr, "WARNING: Device not connected to any VM"
|
|
||||||
exit(0)
|
|
||||||
usb_detach(qvm_collection, attached_to, dev)
|
|
||||||
else:
|
|
||||||
if len(args) > 0:
|
|
||||||
parser.error("Too many parameters")
|
|
||||||
# do_list
|
|
||||||
for dev in usb_list(qvm_collection).values():
|
|
||||||
attached_to = dev['connected-to']
|
|
||||||
attached_to_str = ""
|
|
||||||
if attached_to:
|
|
||||||
attached_to_str = " (attached to %s)" % (attached_to.name)
|
|
||||||
print "%s\t%s%s" % (dev['name'], dev['desc'], attached_to_str)
|
|
||||||
exit (0)
|
|
||||||
|
|
||||||
main()
|
|
@ -148,7 +148,6 @@ fi
|
|||||||
|
|
||||||
systemctl --no-reload enable qubes-core.service >/dev/null 2>&1
|
systemctl --no-reload enable qubes-core.service >/dev/null 2>&1
|
||||||
systemctl --no-reload enable qubes-netvm.service >/dev/null 2>&1
|
systemctl --no-reload enable qubes-netvm.service >/dev/null 2>&1
|
||||||
systemctl --no-reload enable qubes-setupdvm.service >/dev/null 2>&1
|
|
||||||
|
|
||||||
# Conflicts with libxl stack, so disable it
|
# Conflicts with libxl stack, so disable it
|
||||||
systemctl --no-reload disable xend.service >/dev/null 2>&1
|
systemctl --no-reload disable xend.service >/dev/null 2>&1
|
||||||
@ -404,20 +403,17 @@ fi
|
|||||||
|
|
||||||
/usr/lib/qubes/unbind-pci-device.sh
|
/usr/lib/qubes/unbind-pci-device.sh
|
||||||
/usr/lib/qubes/cleanup-dispvms
|
/usr/lib/qubes/cleanup-dispvms
|
||||||
/usr/lib/qubes/qfile-daemon-dvm*
|
|
||||||
/usr/lib/qubes/block-cleaner-daemon.py*
|
/usr/lib/qubes/block-cleaner-daemon.py*
|
||||||
/usr/lib/qubes/vusb-ctl.py*
|
/usr/lib/qubes/vusb-ctl.py*
|
||||||
/usr/lib/qubes/xl-qvm-usb-attach.py*
|
/usr/lib/qubes/xl-qvm-usb-attach.py*
|
||||||
/usr/lib/qubes/xl-qvm-usb-detach.py*
|
/usr/lib/qubes/xl-qvm-usb-detach.py*
|
||||||
/usr/lib/qubes/fix-dir-perms.sh
|
/usr/lib/qubes/fix-dir-perms.sh
|
||||||
/usr/lib/qubes/startup-dvm.sh
|
|
||||||
/usr/lib/qubes/startup-misc.sh
|
/usr/lib/qubes/startup-misc.sh
|
||||||
/usr/lib/qubes/prepare-volatile-img.sh
|
/usr/lib/qubes/prepare-volatile-img.sh
|
||||||
/usr/libexec/qubes/qubes-notify-tools
|
/usr/libexec/qubes/qubes-notify-tools
|
||||||
/usr/libexec/qubes/qubes-notify-updates
|
/usr/libexec/qubes/qubes-notify-updates
|
||||||
%{_unitdir}/qubes-block-cleaner.service
|
%{_unitdir}/qubes-block-cleaner.service
|
||||||
%{_unitdir}/qubes-core.service
|
%{_unitdir}/qubes-core.service
|
||||||
%{_unitdir}/qubes-setupdvm.service
|
|
||||||
%{_unitdir}/qubes-netvm.service
|
%{_unitdir}/qubes-netvm.service
|
||||||
%{_unitdir}/qubes-qmemman.service
|
%{_unitdir}/qubes-qmemman.service
|
||||||
%{_unitdir}/qubes-vm@.service
|
%{_unitdir}/qubes-vm@.service
|
||||||
@ -435,9 +431,6 @@ fi
|
|||||||
/usr/share/qubes/templates/libvirt/devices/pci.xml
|
/usr/share/qubes/templates/libvirt/devices/pci.xml
|
||||||
/usr/share/qubes/templates/libvirt/devices/net.xml
|
/usr/share/qubes/templates/libvirt/devices/net.xml
|
||||||
/usr/lib/tmpfiles.d/qubes.conf
|
/usr/lib/tmpfiles.d/qubes.conf
|
||||||
/usr/lib/qubes/qubes-prepare-saved-domain.sh
|
|
||||||
/usr/lib/qubes/qubes-update-dispvm-savefile-with-progress.sh
|
|
||||||
/etc/xen/scripts/block.qubes
|
|
||||||
/etc/xen/scripts/block-snapshot
|
/etc/xen/scripts/block-snapshot
|
||||||
/etc/xen/scripts/block-origin
|
/etc/xen/scripts/block-origin
|
||||||
/etc/xen/scripts/vif-route-qubes
|
/etc/xen/scripts/vif-route-qubes
|
||||||
@ -456,7 +449,6 @@ fi
|
|||||||
/etc/qubes-rpc/qubes.NotifyUpdates
|
/etc/qubes-rpc/qubes.NotifyUpdates
|
||||||
%attr(2770,root,qubes) %dir /var/log/qubes
|
%attr(2770,root,qubes) %dir /var/log/qubes
|
||||||
%attr(0770,root,qubes) %dir /var/run/qubes
|
%attr(0770,root,qubes) %dir /var/run/qubes
|
||||||
/etc/xdg/autostart/qubes-guid.desktop
|
|
||||||
/etc/xdg/autostart/qrexec-policy-agent.desktop
|
/etc/xdg/autostart/qrexec-policy-agent.desktop
|
||||||
|
|
||||||
/usr/share/doc/qubes/relaxng/*.rng
|
/usr/share/doc/qubes/relaxng/*.rng
|
||||||
|
Loading…
Reference in New Issue
Block a user