Merge branch 'devel-backup'

This commit is contained in:
Marek Marczykowski-Górecki 2017-07-18 01:49:37 +02:00
commit 0c0b625a70
No known key found for this signature in database
GPG Key ID: 063938BA42CFA724
21 changed files with 5324 additions and 17 deletions

View File

@ -1,3 +1,5 @@
[run]
source = qubesadmin
omit = qubesadmin/tests/*
# breaks backup tests for unknown reason
# concurrency=multiprocessing

View File

@ -8,6 +8,7 @@ disable=
bad-continuation,
duplicate-code,
fixme,
cyclic-import,
locally-disabled,
locally-enabled

View File

@ -5,3 +5,5 @@ pylint
sphinx
codecov
python-daemon
mock
lxml

View File

@ -53,10 +53,6 @@ Options
Restore VMs that are already present on the host under different names
.. option:: --force-root
Force to run, even with root privileges
.. option:: --replace-template=REPLACE_TEMPLATE
Restore VMs using another template, syntax:

View File

@ -344,6 +344,8 @@ class QubesBase(qubesadmin.base.PropertyHolder):
raise
for tag in src_vm.tags:
if tag.startswith('created-by-'):
continue
try:
dst_vm.tags.add(tag)
except qubesadmin.exc.QubesException as e:
@ -443,11 +445,12 @@ class QubesLocal(QubesBase):
if not os.path.exists(method_path):
raise qubesadmin.exc.QubesDaemonCommunicationError(
'{} not found'.format(method_path))
qrexec_call_env = os.environ.copy()
qrexec_call_env['QREXEC_REMOTE_DOMAIN'] = 'dom0'
qrexec_call_env['QREXEC_REQUESTED_TARGET'] = dest
proc = subprocess.Popen([method_path, arg], stdin=payload_stream,
stdout=subprocess.PIPE, env=qrexec_call_env)
command = ['env', 'QREXEC_REMOTE_DOMAIN=dom0',
'QREXEC_REQUESTED_TARGET=' + dest, method_path, arg]
if os.getuid() != 0:
command.insert(0, 'sudo')
proc = subprocess.Popen(command, stdin=payload_stream,
stdout=subprocess.PIPE)
payload_stream.close()
(return_data, _) = proc.communicate()
return self._parse_qubesd_response(return_data)

File diff suppressed because it is too large Load Diff

387
qubesadmin/backup/core2.py Normal file
View File

@ -0,0 +1,387 @@
# -*- encoding: utf8 -*-
#
# The Qubes OS Project, http://www.qubes-os.org
#
# Copyright (C) 2017 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 Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License along
# with this program; if not, see <http://www.gnu.org/licenses/>.
'''Parser for core2 qubes.xml'''
import ast
import xml.parsers
import logging
import lxml.etree
from qubesadmin.firewall import Rule, Action, Proto, DstHost, SpecialTarget
import qubesadmin.backup
service_to_feature = {
'ntpd': 'service.ntpd',
'qubes-update-check': 'check-updates',
'meminfo-writer': 'services.meminfo-writer',
}
class Core2VM(qubesadmin.backup.BackupVM):
'''VM object'''
# pylint: disable=too-few-public-methods
def __init__(self):
super(Core2VM, self).__init__()
self.backup_content = False
@property
def included_in_backup(self):
return self.backup_content
@staticmethod
def rule_from_xml_v1(node, action):
'''Parse single rule in old XML format (pre Qubes 4.0)
:param node: XML node for the rule
:param action: action to apply (in old format it wasn't part of the
rule itself)
'''
netmask = node.get('netmask')
if netmask is None:
netmask = 32
else:
netmask = int(netmask)
address = node.get('address')
if address:
dsthost = DstHost(address, netmask)
else:
dsthost = None
proto = node.get('proto')
port = node.get('port')
toport = node.get('toport')
if port and toport:
dstports = port + '-' + toport
elif port:
dstports = port
else:
dstports = None
# backward compatibility: protocol defaults to TCP if port is specified
if dstports and not proto:
proto = 'tcp'
if proto == 'any':
proto = None
expire = node.get('expire')
kwargs = {
'action': action,
}
if dsthost:
kwargs['dsthost'] = dsthost
if dstports:
kwargs['dstports'] = dstports
if proto:
kwargs['proto'] = proto
if expire:
kwargs['expire'] = expire
return Rule(None, **kwargs)
def handle_firewall_xml(self, vm, stream):
'''Load old (Qubes < 4.0) firewall XML format'''
try:
tree = lxml.etree.parse(stream) # pylint: disable=no-member
xml_root = tree.getroot()
policy_v1 = xml_root.get('policy')
assert policy_v1 in ('allow', 'deny')
default_policy_is_accept = (policy_v1 == 'allow')
rules = []
def _translate_action(key):
'''Translate action name'''
if xml_root.get(key, policy_v1) == 'allow':
return Action.accept
return Action.drop
rules.append(Rule(None,
action=_translate_action('dns'),
specialtarget=SpecialTarget('dns')))
rules.append(Rule(None,
action=_translate_action('icmp'),
proto=Proto.icmp))
if default_policy_is_accept:
rule_action = Action.drop
else:
rule_action = Action.accept
for element in xml_root:
rule = self.rule_from_xml_v1(element, rule_action)
rules.append(rule)
if default_policy_is_accept:
rules.append(Rule(None, action='accept'))
else:
rules.append(Rule(None, action='drop'))
vm.firewall.rules = rules
except: # pylint: disable=bare-except
vm.log.exception('Failed to set firewall')
class Core2Qubes(qubesadmin.backup.BackupApp):
'''Parsed qubes.xml'''
def __init__(self, store=None):
if store is None:
raise ValueError("store path required")
self.qid_map = {}
self.log = logging.getLogger('qubesadmin.backup.core2')
super(Core2Qubes, self).__init__(store)
def load_globals(self, element):
'''Load global settings
:param element: XML element containing global settings (root node)
'''
default_netvm = element.get("default_netvm")
if default_netvm is not None:
self.globals['default_netvm'] = self.qid_map[int(default_netvm)] \
if default_netvm != "None" else None
# default_fw_netvm = element.get("default_fw_netvm")
# if default_fw_netvm is not None:
# self.globals['default_fw_netvm'] = \
# self.qid_map[int(default_fw_netvm)] \
# if default_fw_netvm != "None" else None
updatevm = element.get("updatevm")
if updatevm is not None:
self.globals['updatevm'] = self.qid_map[int(updatevm)] \
if updatevm != "None" else None
clockvm = element.get("clockvm")
if clockvm is not None:
self.globals['clockvm'] = self.qid_map[int(clockvm)] \
if clockvm != "None" else None
default_template = element.get("default_template")
self.globals['default_template'] = self.qid_map[int(default_template)] \
if default_template.lower() != "none" else None
def set_netvm_dependency(self, element):
'''Set dependencies between VMs'''
kwargs = {}
attr_list = ("name", "uses_default_netvm", "netvm_qid")
for attribute in attr_list:
kwargs[attribute] = element.get(attribute)
vm = self.domains[kwargs["name"]]
# netvm property
if element.get("uses_default_netvm") is None:
uses_default_netvm = True
else:
uses_default_netvm = (
True if element.get("uses_default_netvm") == "True" else False)
if not uses_default_netvm:
netvm_qid = element.get("netvm_qid")
if netvm_qid is None or netvm_qid == "none":
vm.properties['netvm'] = None
else:
vm.properties['netvm'] = self.qid_map[int(netvm_qid)]
# And DispVM netvm, translated to default_dispvm
if element.get("uses_default_dispvm_netvm") is None:
uses_default_dispvm_netvm = True
else:
uses_default_dispvm_netvm = (
True if element.get("uses_default_dispvm_netvm") == "True"
else False)
if not uses_default_dispvm_netvm:
dispvm_netvm_qid = element.get("dispvm_netvm_qid")
if dispvm_netvm_qid is None or dispvm_netvm_qid == "none":
dispvm_netvm = None
else:
dispvm_netvm = self.qid_map[int(dispvm_netvm_qid)]
else:
dispvm_netvm = vm.properties.get('netvm', self.globals[
'default_netvm'])
if dispvm_netvm != self.globals['default_netvm']:
if dispvm_netvm:
dispvm_tpl_name = 'disp-{}'.format(dispvm_netvm)
else:
dispvm_tpl_name = 'disp-no-netvm'
vm.properties['default_dispvm'] = dispvm_tpl_name
if dispvm_tpl_name not in self.domains:
vm = Core2VM()
vm.name = dispvm_tpl_name
vm.label = 'red'
vm.properties['netvm'] = dispvm_netvm
vm.properties['dispvm_allowed'] = True
vm.backup_content = True
vm.backup_path = None
self.domains[vm.name] = vm
# TODO: add support for #2075
# TODO: set qrexec policy based on dispvm_netvm value
def import_core2_vm(self, element):
'''Parse a single VM from given XML node
This method load only VM properties not depending on other VMs
(other than template). VM connections are set later.
:param element: XML node
'''
vm_class_name = element.tag
vm = Core2VM()
vm.name = element.get('name')
vm.label = element.get('label', 'red')
self.domains[vm.name] = vm
kwargs = {}
if vm_class_name in ["QubesTemplateVm", "QubesTemplateHVm"]:
vm.klass = "TemplateVM"
elif element.get('template_qid').lower() == "none":
kwargs['dir_path'] = element.get('dir_path')
vm.klass = "StandaloneVM"
else:
kwargs['dir_path'] = element.get('dir_path')
vm.template = \
self.qid_map[int(element.get('template_qid'))]
vm.klass = "AppVM"
# simple attributes
for attr, default in {
#'installed_by_rpm': 'False',
'include_in_backups': 'True',
'qrexec_timeout': '60',
'vcpus': '2',
'memory': '400',
'maxmem': '4000',
'default_user': 'user',
'debug': 'False',
'mac': None,
'autostart': 'False'}.items():
value = element.get(attr)
if value and value != default:
vm.properties[attr] = value
# attributes with default value
for attr in ["kernel", "kernelopts"]:
value = element.get(attr)
if value and value.lower() == "none":
value = None
value_is_default = element.get(
"uses_default_{}".format(attr))
if value_is_default and value_is_default.lower() != \
"true":
vm.properties[attr] = value
vm.properties['virt_mode'] = 'hvm' if "HVm" in vm_class_name else 'pv'
if vm_class_name in ('QubesNetVm', 'QubesProxyVm'):
vm.properties['provides_network'] = True
if vm_class_name == 'QubesNetVm':
vm.properties['netvm'] = None
if element.get('internal', False) == 'True':
vm.features['internal'] = True
services = element.get('services')
if services:
services = ast.literal_eval(services)
else:
services = {}
for service, value in services.items():
feature = service
for repl_service, repl_feature in \
service_to_feature.items():
if repl_service == service:
feature = repl_feature
vm.features[feature] = value
vm.backup_content = element.get('backup_content', False) == 'True'
vm.backup_path = element.get('backup_path', None)
vm.size = element.get('backup_size', 0)
pci_strictreset = element.get('pci_strictreset', True)
pcidevs = element.get('pcidevs')
if pcidevs:
pcidevs = ast.literal_eval(pcidevs)
for pcidev in pcidevs:
if not pci_strictreset:
vm.devices['pci'][('dom0', pcidev.replace(':', '_'))] = {
'no-strict-reset': True}
else:
vm.devices['pci'][('dom0', pcidev.replace(':', '_'))] = {}
def load(self):
with open(self.store) as fh:
try:
# pylint: disable=no-member
tree = lxml.etree.parse(fh)
except (EnvironmentError, # pylint: disable=broad-except
xml.parsers.expat.ExpatError) as err:
self.log.error(err)
return False
self.globals['default_kernel'] = tree.getroot().get("default_kernel")
vm_classes = ["AdminVM", "TemplateVm", "TemplateHVm",
"AppVm", "HVm", "NetVm", "ProxyVm"]
# First build qid->name map
for vm_class_name in vm_classes:
vms_of_class = tree.findall("Qubes" + vm_class_name)
for element in vms_of_class:
qid = element.get('qid', None)
name = element.get('name', None)
if qid and name:
self.qid_map[int(qid)] = name
# Qubes R2 din't have dom0 in qubes.xml
if 0 not in self.qid_map:
vm = Core2VM()
vm.name = 'dom0'
vm.klass = 'AdminVM'
vm.label = 'black'
self.domains['dom0'] = vm
self.qid_map[0] = 'dom0'
# Then load all VMs - since we have qid_map, no need to preserve
# specific load older.
for vm_class_name in vm_classes:
vms_of_class = tree.findall("Qubes" + vm_class_name)
for element in vms_of_class:
self.import_core2_vm(element)
# ... and load other VMs
for vm_class_name in ["AppVm", "HVm", "NetVm", "ProxyVm"]:
vms_of_class = tree.findall("Qubes" + vm_class_name)
# first non-template based, then template based
sorted_vms_of_class = sorted(vms_of_class,
key=lambda x: str(x.get('template_qid')).lower() != "none")
for element in sorted_vms_of_class:
self.import_core2_vm(element)
# and load other defaults (default netvm, updatevm etc)
self.load_globals(tree.getroot())
# After importing all VMs, set netvm references, in the same order
for vm_class_name in vm_classes:
for element in tree.findall("Qubes" + vm_class_name):
try:
self.set_netvm_dependency(element)
except (ValueError, LookupError) as err:
self.log.error("VM %s: failed to set netvm dependency: %s",
element.get('name'), err)

162
qubesadmin/backup/core3.py Normal file
View File

@ -0,0 +1,162 @@
# -*- encoding: utf8 -*-
#
# The Qubes OS Project, http://www.qubes-os.org
#
# Copyright (C) 2017 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 Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License along
# with this program; if not, see <http://www.gnu.org/licenses/>.
'''Parser for core2 qubes.xml'''
import xml.parsers
import logging
import lxml.etree
import qubesadmin.backup
import qubesadmin.firewall
class Core3VM(qubesadmin.backup.BackupVM):
'''VM object'''
# pylint: disable=too-few-public-methods
@property
def included_in_backup(self):
return self.backup_path is not None
def handle_firewall_xml(self, vm, stream):
'''Load new (Qubes >= 4.0) firewall XML format'''
try:
tree = lxml.etree.parse(stream) # pylint: disable=no-member
xml_root = tree.getroot()
rules = []
for rule_node in xml_root.findall('./rules/rule'):
rule_opts = {}
for rule_opt in rule_node.findall('./properties/property'):
rule_opts[rule_opt.get('name')] = rule_opt.text
rules.append(qubesadmin.firewall.Rule(None, **rule_opts))
vm.firewall.rules = rules
except: # pylint: disable=bare-except
vm.log.exception('Failed to set firewall')
class Core3Qubes(qubesadmin.backup.BackupApp):
'''Parsed qubes.xml'''
def __init__(self, store=None):
if store is None:
raise ValueError("store path required")
self.log = logging.getLogger('qubesadmin.backup.core3')
self.labels = {}
super(Core3Qubes, self).__init__(store)
@staticmethod
def get_property(xml_obj, prop):
'''Get property of given object (XML node)
Object can be any PropertyHolder serialized to XML - in practice
:py:class:`BaseVM` or :py:class:`Qubes`.
'''
xml_prop = xml_obj.findall('./property[@name=\'{}\']'.format(prop))
if not xml_prop:
raise KeyError(prop)
return xml_prop[0].text
def load_labels(self, labels_element):
'''Load labels table'''
for node in labels_element.findall('label'):
ident = node.get('id')
assert ident is not None
self.labels[ident] = node.text
def load_globals(self, globals_element):
'''Load global settings
:param globals_element: XML element containing global settings
'''
for node in globals_element.findall('property'):
name = node.get('name')
assert name is not None
self.globals[name] = node.text
def import_core3_vm(self, element):
'''Parse a single VM from given XML node
This method load only VM properties not depending on other VMs
(other than template). VM connections are set later.
:param element: XML node
'''
vm = Core3VM()
vm.klass = element.get('class')
for node in element.findall('./properties/property'):
name = node.get('name')
assert name is not None
vm.properties[name] = node.text
for node in element.findall('./features/feature'):
name = node.get('name')
assert name is not None
vm.features[name] = False if node.text is None else node.text
for node in element.findall('./tags/tag'):
name = node.get('name')
assert name is not None
vm.tags.add(name)
for bus_node in element.findall('./devices'):
bus_name = bus_node.get('class')
assert bus_name is not None
for node in bus_node.findall('./device'):
backend_domain = node.get('backend-domain')
ident = node.get('id')
options = {}
for opt_node in node.findall('./option'):
opt_name = opt_node.get('name')
options[opt_name] = opt_node.text
vm.devices[bus_name][(backend_domain, ident)] = options
# extract base properties
if vm.klass == 'AdminVM':
vm.name = 'dom0'
else:
vm.name = vm.properties.pop('name')
vm.label = self.labels[vm.properties.pop('label')]
vm.template = vm.properties.pop('template', None)
# skip UUID and qid, will be generated during restore
vm.properties.pop('uuid', None)
vm.properties.pop('qid', None)
if vm.features.pop('backup-content', False):
vm.backup_path = vm.features.pop('backup-path', None)
vm.size = vm.features.pop('backup-size', 0)
self.domains[vm.name] = vm
def load(self):
with open(self.store) as fh:
try:
# pylint: disable=no-member
tree = lxml.etree.parse(fh)
except (EnvironmentError, # pylint: disable=broad-except
xml.parsers.expat.ExpatError) as err:
self.log.error(err)
return False
self.load_labels(tree.find('./labels'))
for element in tree.findall('./domains/domain'):
self.import_core3_vm(element)
# and load other defaults (default netvm, updatevm etc)
self.load_globals(tree.find('./properties'))

View File

@ -144,7 +144,7 @@ class DeviceCollection(object):
options = device_assignment.options.copy()
if device_assignment.persistent:
options['persistent'] = 'yes'
options['persistent'] = 'True'
options_str = ' '.join('{}={}'.format(opt,
val) for opt, val in sorted(options.items()))
self._vm.qubesd_call(None,

View File

@ -42,7 +42,7 @@ class Features(object):
self.vm.qubesd_call(self.vm.name, 'admin.vm.feature.Remove', key)
def __setitem__(self, key, value):
if not value:
if value is False:
# False value needs to be serialized as empty string
self.vm.qubesd_call(self.vm.name, 'admin.vm.feature.Set', key, b'')
else:

View File

@ -88,7 +88,10 @@ class DstHost(RuleOption):
# add prefix length to bare IP addresses
try:
socket.inet_pton(socket.AF_INET6, value)
self.prefixlen = prefixlen or 128
if prefixlen is not None:
self.prefixlen = prefixlen
else:
self.prefixlen = 128
if self.prefixlen < 0 or self.prefixlen > 128:
raise ValueError(
'netmask for IPv6 must be between 0 and 128')
@ -100,7 +103,10 @@ class DstHost(RuleOption):
if value.count('.') != 3:
raise ValueError(
'Invalid number of dots in IPv4 address')
self.prefixlen = prefixlen or 32
if prefixlen is not None:
self.prefixlen = prefixlen
else:
self.prefixlen = 32
if self.prefixlen < 0 or self.prefixlen > 32:
raise ValueError(
'netmask for IPv4 must be between 0 and 32')
@ -137,6 +143,10 @@ class DstHost(RuleOption):
@property
def rule(self):
'''API representation of this rule element'''
if self.prefixlen == 0 and self.type != 'dsthost':
# 0.0.0.0/0 or ::/0, doesn't limit to any particular host,
# so skip it
return None
return self.type + '=' + str(self)

View File

@ -0,0 +1,279 @@
# -*- encoding: utf8 -*-
#
# The Qubes OS Project, http://www.qubes-os.org
#
# Copyright (C) 2017 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 Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License along
# with this program; if not, see <http://www.gnu.org/licenses/>.
import hashlib
import logging
import multiprocessing
import os
import shutil
import qubesadmin.backup
import qubesadmin.exc
import qubesadmin.tests
SIGNATURE_LEN = 512
class BackupTestCase(qubesadmin.tests.QubesTestCase):
class BackupErrorHandler(logging.Handler):
def __init__(self, errors_queue, level=logging.NOTSET):
super(BackupTestCase.BackupErrorHandler, self).__init__(level)
self.errors_queue = errors_queue
def emit(self, record):
self.errors_queue.put(record.getMessage())
def make_vm_name(self, name):
try:
return super(BackupTestCase, self).make_vm_name(name)
except AttributeError:
return 'test-' + name
def setUp(self):
super(BackupTestCase, self).setUp()
self.error_detected = multiprocessing.Queue()
self.log = logging.getLogger('qubesadmin.tests.backup')
self.log.debug("Creating backupvm")
self.backupdir = os.path.join(os.environ["HOME"], "test-backup")
if os.path.exists(self.backupdir):
shutil.rmtree(self.backupdir)
os.mkdir(self.backupdir)
self.error_handler = self.BackupErrorHandler(self.error_detected,
level=logging.WARNING)
backup_log = logging.getLogger('qubesadmin.backup')
backup_log.addHandler(self.error_handler)
def tearDown(self):
super(BackupTestCase, self).tearDown()
shutil.rmtree(self.backupdir)
backup_log = logging.getLogger('qubes.backup')
backup_log.removeHandler(self.error_handler)
def fill_image(self, path, size=None, sparse=False, signature=b''):
block_size = 4096
self.log.debug("Filling %s" % path)
f = open(path, 'wb+')
if size is None:
f.seek(0, 2)
size = f.tell()
f.seek(0)
f.write(signature)
f.write(b'\0' * (SIGNATURE_LEN - len(signature)))
for block_num in range(int(size/block_size)):
if sparse:
f.seek(block_size, 1)
f.write(b'a' * block_size)
f.close()
# NOTE: this was create_basic_vms
def create_backup_vms(self, pool=None):
template = self.app.default_template
vms = []
vmname = self.make_vm_name('test-net')
self.log.debug("Creating %s" % vmname)
testnet = self.app.add_new_vm('AppVM',
name=vmname,
label='red')
testnet.provides_network = True
testnet.create_on_disk(pool=pool)
testnet.features['services/ntpd'] = True
vms.append(testnet)
self.fill_image(testnet.storage.export('private'), 20*1024*1024)
vmname = self.make_vm_name('test1')
self.log.debug("Creating %s" % vmname)
testvm1 = self.app.add_new_vm('AppVM',
name=vmname, template=template, label='red')
testvm1.uses_default_netvm = False
testvm1.netvm = testnet
testvm1.create_on_disk(pool=pool)
vms.append(testvm1)
self.fill_image(testvm1.storage.export('private'), 100 * 1024 * 1024)
vmname = self.make_vm_name('testhvm1')
self.log.debug("Creating %s" % vmname)
testvm2 = self.app.add_new_vm('StandaloneVM',
name=vmname,
label='red')
testvm2.hvm = True
testvm2.create_on_disk(pool=pool)
self.fill_image(testvm2.storage.export('root'), 1024 * 1024 * 1024, \
True)
vms.append(testvm2)
vmname = self.make_vm_name('template')
self.log.debug("Creating %s" % vmname)
testvm3 = self.app.add_new_vm('TemplateVM',
name=vmname, label='red')
testvm3.create_on_disk(pool=pool)
self.fill_image(testvm3.storage.export('root'), 100 * 1024 * 1024, True)
vms.append(testvm3)
vmname = self.make_vm_name('custom')
self.log.debug("Creating %s" % vmname)
testvm4 = self.app.add_new_vm('AppVM',
name=vmname, template=testvm3, label='red')
testvm4.create_on_disk(pool=pool)
vms.append(testvm4)
self.app.save()
return vms
def make_backup(self, vms, target=None, expect_failure=False, **kwargs):
if target is None:
target = self.backupdir
try:
backup = qubesadmin.backup.Backup(self.app, vms, **kwargs)
except qubesadmin.exc.QubesException as e:
if not expect_failure:
self.fail("QubesException during backup_prepare: %s" % str(e))
else:
raise
if 'passphrase' not in kwargs:
backup.passphrase = 'qubes'
backup.target_dir = target
try:
backup.backup_do()
except qubesadmin.exc.QubesException as e:
if not expect_failure:
self.fail("QubesException during backup_do: %s" % str(e))
else:
raise
def restore_backup(self, source=None, appvm=None, options=None,
expect_errors=None, manipulate_restore_info=None,
passphrase='qubes'):
if source is None:
backupfile = os.path.join(self.backupdir,
sorted(os.listdir(self.backupdir))[-1])
else:
backupfile = source
with self.assertNotRaises(qubesadmin.exc.QubesException):
restore_op = qubesadmin.backup.BackupRestore(
self.app, backupfile, appvm, passphrase)
if options:
for key, value in options.items():
setattr(restore_op.options, key, value)
restore_info = restore_op.get_restore_info()
if callable(manipulate_restore_info):
restore_info = manipulate_restore_info(restore_info)
self.log.debug(restore_op.get_restore_summary(restore_info))
with self.assertNotRaises(qubesadmin.exc.QubesException):
restore_op.restore_do(restore_info)
errors = []
if expect_errors is None:
expect_errors = []
else:
self.assertFalse(self.error_detected.empty(),
"Restore errors expected, but none detected")
while not self.error_detected.empty():
current_error = self.error_detected.get()
if any(map(current_error.startswith, expect_errors)):
continue
errors.append(current_error)
self.assertTrue(len(errors) == 0,
"Error(s) detected during backup_restore_do: %s" %
'\n'.join(errors))
if not appvm and not os.path.isdir(backupfile):
os.unlink(backupfile)
def create_sparse(self, path, size, signature=b''):
f = open(path, "wb")
f.write(signature)
f.write(b'\0' * (SIGNATURE_LEN - len(signature)))
f.truncate(size)
f.close()
def vm_checksum(self, vms):
hashes = {}
for vm in vms:
assert isinstance(vm, qubesadmin.vm.QubesVM)
hashes[vm.name] = {}
for name, volume in vm.volumes.items():
if not volume.rw or not volume.save_on_stop:
continue
vol_path = vm.storage.get_pool(volume).export(volume)
hasher = hashlib.sha1()
with open(vol_path, 'rb') as afile:
for buf in iter(lambda: afile.read(4096000), b''):
hasher.update(buf)
hashes[vm.name][name] = hasher.hexdigest()
return hashes
def assertCorrectlyRestored(self, orig_vms, orig_hashes):
''' Verify if restored VMs are identical to those before backup.
:param orig_vms: collection of original QubesVM objects
:param orig_hashes: result of :py:meth:`vm_checksum` on original VMs,
before backup
:return:
'''
for vm in orig_vms:
self.assertIn(vm.name, self.app.domains)
restored_vm = self.app.domains[vm.name]
for prop in ('name', 'kernel',
'memory', 'maxmem', 'kernelopts',
'services', 'vcpus', 'features'
'include_in_backups', 'default_user', 'qrexec_timeout',
'autostart', 'pci_strictreset', 'debug',
'internal'):
if not hasattr(vm, prop):
continue
self.assertEqual(
getattr(vm, prop), getattr(restored_vm, prop),
"VM {} - property {} not properly restored".format(
vm.name, prop))
for prop in ('netvm', 'template', 'label'):
if not hasattr(vm, prop):
continue
orig_value = getattr(vm, prop)
restored_value = getattr(restored_vm, prop)
if orig_value and restored_value:
self.assertEqual(orig_value.name, restored_value.name,
"VM {} - property {} not properly restored".format(
vm.name, prop))
else:
self.assertEqual(orig_value, restored_value,
"VM {} - property {} not properly restored".format(
vm.name, prop))
for dev_class in vm.devices.keys():
for dev in vm.devices[dev_class]:
self.assertIn(dev, restored_vm.devices[dev_class],
"VM {} - {} device not restored".format(
vm.name, dev_class))
if orig_hashes:
hashes = self.vm_checksum([restored_vm])[restored_vm.name]
self.assertEqual(orig_hashes[vm.name], hashes,
"VM {} - disk images are not properly restored".format(
vm.name))

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,7 @@
<QubesFirewallRules dns="allow" icmp="allow" policy="deny" yumProxy="allow">
<rule address="0.0.0.0" proto="tcp" netmask="0" port="22"/>
<rule address="0.0.0.0" proto="tcp" netmask="0" port="9418"/>
<rule address="192.168.0.1" proto="tcp" port="1234"/>
<rule address="fedorahosted.org" proto="tcp" port="443"/>
<rule address="xenbits.xen.org" proto="tcp" port="80"/>
</QubesFirewallRules>

View File

@ -0,0 +1,18 @@
<QubesVmCollection updatevm="3" default_kernel="3.7.6-2" default_netvm="3" default_fw_netvm="2" default_template="1" clockvm="2">
<QubesTemplateVm installed_by_rpm="True" kernel="3.7.6-2" uses_default_kernelopts="True" qid="1" include_in_backups="True" uses_default_kernel="True" qrexec_timeout="60" internal="False" conf_file="fedora-20-x64.conf" label="black" template_qid="none" kernelopts="" memory="400" default_user="user" netvm_qid="3" uses_default_netvm="True" volatile_img="volatile.img" services="{ 'meminfo-writer': True}" maxmem="1535" pcidevs="[]" name="fedora-20-x64" private_img="private.img" vcpus="2" root_img="root.img" debug="False" dir_path="/var/lib/qubes/vm-templates/fedora-20-x64"/>
<QubesNetVm installed_by_rpm="False" kernel="3.7.6-2" uses_default_kernelopts="True" qid="2" include_in_backups="True" uses_default_kernel="True" qrexec_timeout="60" internal="False" conf_file="netvm.conf" label="red" template_qid="1" kernelopts="iommu=soft swiotlb=4096" memory="200" default_user="user" volatile_img="volatile.img" services="{'ntpd': False, 'meminfo-writer': False}" maxmem="1535" pcidevs="['02:00.0', '03:00.0']" name="netvm" netid="1" private_img="private.img" vcpus="2" root_img="root.img" debug="False" dir_path="/var/lib/qubes/servicevms/netvm"/>
<QubesProxyVm installed_by_rpm="False" kernel="3.7.6-2" uses_default_kernelopts="True" qid="3" include_in_backups="True" uses_default_kernel="True" qrexec_timeout="60" internal="False" conf_file="firewallvm.conf" label="green" template_qid="1" kernelopts="" memory="200" default_user="user" netvm_qid="2" volatile_img="volatile.img" services="{'meminfo-writer': True}" maxmem="1535" pcidevs="[]" name="firewallvm" netid="2" private_img="private.img" vcpus="2" root_img="root.img" debug="False" dir_path="/var/lib/qubes/servicevms/firewallvm"/>
<QubesAppVm installed_by_rpm="False" kernel="3.7.6-2" uses_default_kernelopts="True" qid="4" include_in_backups="True" uses_default_kernel="True" qrexec_timeout="60" internal="True" conf_file="fedora-20-x64-dvm.conf" label="gray" template_qid="1" kernelopts="" memory="400" default_user="user" netvm_qid="3" uses_default_netvm="True" volatile_img="volatile.img" services="{ 'meminfo-writer': True}" maxmem="1535" pcidevs="[]" name="fedora-20-x64-dvm" private_img="private.img" vcpus="1" root_img="root.img" debug="False" dir_path="/var/lib/qubes/appvms/fedora-20-x64-dvm"/>
<QubesAppVm backup_content="True" backup_path="appvms/test-work" installed_by_rpm="False" kernel="3.7.6-2" uses_default_kernelopts="True" qid="5" include_in_backups="True" uses_default_kernel="True" qrexec_timeout="60" internal="False" conf_file="test-work.conf" label="green" template_qid="1" kernelopts="" memory="400" default_user="user" netvm_qid="3" uses_default_netvm="True" volatile_img="volatile.img" services="{'meminfo-writer': True}" maxmem="1535" pcidevs="[]" name="test-work" private_img="private.img" vcpus="2" root_img="root.img" debug="False" dir_path="/var/lib/qubes/appvms/test-work"/>
<QubesAppVm installed_by_rpm="False" kernel="3.7.6-2" uses_default_kernelopts="True" qid="6" include_in_backups="True" uses_default_kernel="True" qrexec_timeout="60" internal="False" conf_file="banking.conf" label="green" template_qid="1" kernelopts="" memory="400" default_user="user" netvm_qid="3" uses_default_netvm="True" volatile_img="volatile.img" services="{'meminfo-writer': True}" maxmem="1535" pcidevs="[]" name="banking" private_img="private.img" vcpus="2" root_img="root.img" debug="False" dir_path="/var/lib/qubes/appvms/banking"/>
<QubesAppVm installed_by_rpm="False" kernel="3.7.6-2" uses_default_kernelopts="True" qid="7" include_in_backups="True" uses_default_kernel="True" qrexec_timeout="60" internal="False" conf_file="personal.conf" label="yellow" template_qid="1" kernelopts="" memory="400" default_user="user" netvm_qid="3" uses_default_netvm="True" volatile_img="volatile.img" services="{'meminfo-writer': True}" maxmem="1535" pcidevs="[]" name="personal" private_img="private.img" vcpus="2" root_img="root.img" debug="False" dir_path="/var/lib/qubes/appvms/personal"/>
<QubesAppVm installed_by_rpm="False" kernel="3.7.6-2" uses_default_kernelopts="True" qid="8" include_in_backups="True" uses_default_kernel="True" qrexec_timeout="60" internal="False" conf_file="untrusted.conf" label="red" template_qid="1" kernelopts="" memory="400" default_user="user" netvm_qid="12" uses_default_netvm="False" volatile_img="volatile.img" services="{'meminfo-writer': True}" maxmem="1535" pcidevs="[]" name="untrusted" private_img="private.img" vcpus="2" root_img="root.img" debug="False" dir_path="/var/lib/qubes/appvms/untrusted"/>
<QubesTemplateVm backup_size="104857600" backup_content="True" backup_path="vm-templates/test-template-clone" installed_by_rpm="False" kernel="3.7.6-2" uses_default_kernelopts="True" qid="9" include_in_backups="True" uses_default_kernel="True" qrexec_timeout="60" internal="False" conf_file="test-template-clone.conf" label="green" template_qid="none" kernelopts="" memory="400" default_user="user" netvm_qid="3" uses_default_netvm="True" volatile_img="volatile.img" services="{'meminfo-writer': True}" maxmem="1535" pcidevs="[]" name="test-template-clone" private_img="private.img" vcpus="2" root_img="root.img" debug="False" dir_path="/var/lib/qubes/vm-templates/test-template-clone"/>
<QubesAppVm backup_size="104857600" backup_content="True" backup_path="appvms/test-custom-template-appvm" installed_by_rpm="False" kernel="3.7.6-2" uses_default_kernelopts="True" qid="10" include_in_backups="True" uses_default_kernel="True" qrexec_timeout="60" internal="False" conf_file="test-custom-template-appvm.conf" label="yellow" template_qid="9" kernelopts="" memory="400" default_user="user" netvm_qid="3" uses_default_netvm="True" volatile_img="volatile.img" services="{'meminfo-writer': True}" maxmem="1535" pcidevs="[]" name="test-custom-template-appvm" private_img="private.img" vcpus="2" root_img="root.img" debug="False" dir_path="/var/lib/qubes/appvms/test-custom-template-appvm"/>
<QubesAppVm backup_size="104857600" backup_content="True" backup_path="appvms/test-standalonevm" installed_by_rpm="False" kernel="3.7.6-2" uses_default_kernelopts="True" qid="11" include_in_backups="True" uses_default_kernel="True" qrexec_timeout="60" internal="False" conf_file="test-standalonevm.conf" label="blue" template_qid="none" kernelopts="" memory="400" default_user="user" netvm_qid="3" uses_default_netvm="True" volatile_img="volatile.img" services="{'meminfo-writer': True}" maxmem="1535" pcidevs="[]" name="test-standalonevm" private_img="private.img" vcpus="2" root_img="root.img" debug="False" dir_path="/var/lib/qubes/appvms/test-standalonevm"/>
<QubesProxyVm backup_size="104857600" backup_content="True" backup_path="servicevms/test-testproxy" installed_by_rpm="False" kernel="3.7.6-2" uses_default_kernelopts="True" qid="12" include_in_backups="True" uses_default_kernel="True" qrexec_timeout="60" internal="False" conf_file="test-testproxy.conf" label="red" template_qid="1" kernelopts="" memory="200" default_user="user" netvm_qid="3" volatile_img="volatile.img" services="{'meminfo-writer': True}" maxmem="1535" pcidevs="[]" name="test-testproxy" netid="3" private_img="private.img" vcpus="2" root_img="root.img" debug="False" dir_path="/var/lib/qubes/servicevms/test-testproxy"/>
<QubesProxyVm installed_by_rpm="False" kernel="3.7.6-2" uses_default_kernelopts="True" qid="13" include_in_backups="True" uses_default_kernel="True" qrexec_timeout="60" internal="False" conf_file="testproxy2.conf" label="red" template_qid="9" kernelopts="" memory="200" default_user="user" netvm_qid="2" volatile_img="volatile.img" services="{'meminfo-writer': True}" maxmem="1535" pcidevs="[]" name="testproxy2" netid="4" private_img="private.img" vcpus="2" root_img="root.img" debug="False" dir_path="/var/lib/qubes/servicevms/testproxy2"/>
<QubesHVm backup_size="104857600" backup_content="True" backup_path="appvms/test-testhvm" installed_by_rpm="False" netvm_qid="none" qid="14" include_in_backups="True" timezone="localtime" qrexec_timeout="60" conf_file="test-testhvm.conf" label="purple" template_qid="none" internal="False" memory="512" uses_default_netvm="True" services="{'meminfo-writer': False}" default_user="user" pcidevs="[]" name="test-testhvm" qrexec_installed="False" private_img="private.img" drive="None" vcpus="2" root_img="root.img" guiagent_installed="False" debug="False" dir_path="/var/lib/qubes/appvms/test-testhvm"/>
<QubesDisposableVm dispid="50" firewall_conf="firewall.xml" label="red" name="disp50" netvm_qid="2" qid="15" template_qid="1"/>
<QubesNetVm backup_size="104857600" backup_content="True" backup_path="servicevms/test-net" installed_by_rpm="False" kernel="3.7.6-2" uses_default_kernelopts="True" qid="16" include_in_backups="True" uses_default_kernel="True" qrexec_timeout="60" internal="False" conf_file="test-net.conf" label="red" template_qid="1" kernelopts="iommu=soft swiotlb=4096" memory="200" default_user="user" volatile_img="volatile.img" services="{'ntpd': False, 'meminfo-writer': False}" maxmem="1535" pcidevs="['02:00.0', '03:00.0']" name="test-net" netid="2" private_img="private.img" vcpus="2" root_img="root.img" debug="False" dir_path="/var/lib/qubes/servicevms/test-net"/>
</QubesVmCollection>

View File

@ -0,0 +1,43 @@
<firewall version="2">
<rules>
<rule>
<properties>
<property name="action">accept</property>
<property name="specialtarget">dns</property>
</properties>
</rule>
<rule>
<properties>
<property name="action">accept</property>
<property name="proto">icmp</property>
</properties>
</rule>
<rule>
<properties>
<property name="action">accept</property>
<property name="proto">tcp</property>
<property name="dstports">22</property>
</properties>
</rule>
<rule>
<properties>
<property name="action">accept</property>
<property name="dsthost">www.qubes-os.org</property>
<property name="proto">tcp</property>
<property name="dstports">443</property>
</properties>
</rule>
<rule>
<properties>
<property name="action">accept</property>
<property name="dsthost">192.168.0.0/24</property>
<property name="proto">tcp</property>
</properties>
</rule>
<rule>
<properties>
<property name="action">drop</property>
</properties>
</rule>
</rules>
</firewall>

View File

@ -0,0 +1,526 @@
<qubes>
<labels>
<label color="0xcc0000" id="label-1">red</label>
<label color="0xf57900" id="label-2">orange</label>
<label color="0xedd400" id="label-3">yellow</label>
<label color="0x73d216" id="label-4">green</label>
<label color="0x555753" id="label-5">gray</label>
<label color="0x3465a4" id="label-6">blue</label>
<label color="0x75507b" id="label-7">purple</label>
<label color="0x000000" id="label-8">black</label>
</labels>
<pools>
<pool driver="lvm_thin" name="lvm" thin_pool="pool3" volume_group="core3"/>
<pool dir_path="/var/lib/qubes/vm-kernels" driver="linux-kernel" name="linux-kernel"/>
<pool dir_path="/var/lib/qubes" driver="file" name="default" revisions_to_keep="1"/>
</pools>
<properties>
<property name="clockvm">sys-net</property>
<property name="default_dispvm">fedora-25-dvm</property>
<property name="default_kernel">4.9.31-17</property>
<property name="default_netvm">sys-firewall</property>
<property name="default_template">fedora-25</property>
<property name="updatevm">sys-firewall</property>
</properties>
<domains>
<domain id="domain-20" class="AppVM">
<properties>
<property name="debug">True</property>
<property name="kernel"></property>
<property name="label">label-5</property>
<property name="name">test-d8test</property>
<property name="qid">20</property>
<property name="uuid">9204481c-7e37-42a6-8a66-b4cc63d65f11</property>
<property name="template">debian-8</property>
</properties>
<features>
<feature name="backup-content">True</feature>
<feature name="backup-path">appvms/test-d8test</feature>
<feature name="backup-size">20971520</feature>
</features>
<devices class="pci"/>
<devices class="block"/>
<tags>
<tag name="created-by-dom0"/>
</tags>
<volume-config>
<volume name="kernel" pool="linux-kernel" revisions_to_keep="0" vid=""/>
<volume name="root" pool="default" revisions_to_keep="0" size="10737418240" snap_on_start="True" source="vm-templates/debian-8/root" vid="appvms/test-d8test/root"/>
<volume name="volatile" pool="default" revisions_to_keep="0" rw="True" size="10737418240" vid="appvms/test-d8test/volatile"/>
<volume name="private" pool="default" revisions_to_keep="1" rw="True" save_on_stop="True" size="2147483648" vid="appvms/test-d8test/private"/>
</volume-config>
</domain>
<domain id="domain-30" class="AppVM">
<properties>
<property name="label">label-1</property>
<property name="name">test-proxy</property>
<property name="qid">30</property>
<property name="uuid">367c64e6-ab8c-42df-91b6-c9d4c7d015f2</property>
<property name="template">debian-8</property>
<property name="provides_network">True</property>
<property name="netvm">sys-net</property>
</properties>
<features>
<feature name="backup-content">True</feature>
<feature name="backup-path">appvms/test-proxy</feature>
<feature name="backup-size">209715200</feature>
</features>
<devices class="pci"/>
<devices class="block"/>
<tags>
<tag name="created-by-dom0"/>
</tags>
<volume-config>
<volume name="kernel" pool="linux-kernel" revisions_to_keep="0" vid="4.9.31-17"/>
<volume name="root" pool="default" revisions_to_keep="0" size="10737418240" snap_on_start="True" source="vm-templates/debian-8/root" vid="appvms/d8test2/root"/>
<volume name="volatile" pool="default" revisions_to_keep="0" rw="True" size="10737418240" vid="appvms/d8test2/volatile"/>
<volume name="private" pool="default" revisions_to_keep="1" rw="True" save_on_stop="True" size="2147483648" vid="appvms/d8test2/private"/>
</volume-config>
</domain>
<domain id="domain-16" class="TemplateVM">
<properties>
<property name="label">label-8</property>
<property name="name">debian-8</property>
<property name="qid">16</property>
<property name="uuid">0e2fa953-016f-4486-9e36-c9b386fc2bac</property>
</properties>
<features>
<feature name="qrexec">True</feature>
<feature name="updates-available"></feature>
<feature name="gui">1</feature>
</features>
<devices class="pci"/>
<devices class="block"/>
<tags>
<tag name="created-by-dom0"/>
</tags>
<volume-config>
<volume name="kernel" pool="linux-kernel" revisions_to_keep="0" vid="4.9.31-17"/>
<volume name="root" pool="default" revisions_to_keep="1" rw="True" save_on_stop="True" size="10737418240" vid="vm-templates/debian-8/root"/>
<volume name="volatile" pool="default" revisions_to_keep="0" rw="True" size="10737418240" vid="vm-templates/debian-8/volatile"/>
<volume name="private" pool="default" revisions_to_keep="0" rw="True" save_on_stop="True" size="2147483648" vid="vm-templates/debian-8/private"/>
</volume-config>
</domain>
<domain id="domain-0" class="AdminVM">
<properties>
<property name="label">label-8</property>
</properties>
<features/>
<devices class="pci"/>
<devices class="block"/>
<devices class="usb"/>
<tags/>
</domain>
<domain id="domain-31" class="AppVM">
<properties>
<property name="debug">True</property>
<property name="kernel"></property>
<property name="label">label-3</property>
<property name="name">test-custom-template-appvm</property>
<property name="qid">31</property>
<property name="uuid">1bdb7b32-8a4b-4bb7-8da0-6b06494f642c</property>
<property name="template">test-fedora-25-clone</property>
</properties>
<features>
<feature name="backup-content">True</feature>
<feature name="backup-path">appvms/test-custom-template-appvm</feature>
<feature name="backup-size">20971520</feature>
</features>
<devices class="pci"/>
<devices class="block"/>
<devices class="usb"/>
<tags>
<tag name="created-by-dom0"/>
</tags>
<volume-config>
<volume name="kernel" pool="linux-kernel" revisions_to_keep="0" vid=""/>
<volume name="root" pool="default" revisions_to_keep="0" size="10737418240" snap_on_start="True" source="vm-templates/fedora-25/root" vid="appvms/test-f25test2/root"/>
<volume name="volatile" pool="default" revisions_to_keep="0" rw="True" size="10737418240" vid="appvms/test-f25test2/volatile"/>
<volume name="private" pool="default" revisions_to_keep="1" rw="True" save_on_stop="True" size="2147483648" vid="appvms/test-f25test2/private"/>
</volume-config>
</domain>
<domain id="domain-7" class="TemplateVM">
<properties>
<property name="label">label-8</property>
<property name="maxmem">4000</property>
<property name="name">test-fedora-25-clone</property>
<property name="qid">7</property>
<property name="uuid">e3b8d458-8c4c-4ccb-b00d-b7ae454cb08f</property>
</properties>
<features>
<feature name="service.meminfo-writer">1</feature>
<feature name="backup-content">True</feature>
<feature name="backup-path">vm-templates/test-fedora-25-clone</feature>
<feature name="backup-size">2097152000</feature>
</features>
<devices class="pci"/>
<devices class="block"/>
<tags/>
<volume-config>
<volume name="kernel" pool="linux-kernel" revisions_to_keep="0" vid="4.9.31-17"/>
<volume name="root" pool="default" revisions_to_keep="1" rw="True" save_on_stop="True" size="10737418240" vid="vm-templates/test-fedora-25-clone/root"/>
<volume name="volatile" pool="default" revisions_to_keep="0" rw="True" size="10737418240" vid="vm-templates/test-fedora-25-clone/volatile"/>
<volume name="private" pool="default" revisions_to_keep="0" rw="True" save_on_stop="True" size="2147483648" vid="vm-templates/test-fedora-25-clone/private"/>
</volume-config>
</domain>
<domain id="domain-12" class="AppVM">
<properties>
<property name="dispvm_allowed">True</property>
<property name="label">label-1</property>
<property name="name">fedora-25-clone-dvm</property>
<property name="qid">10</property>
<property name="uuid">30daa6b5-693b-460a-9da7-99078a2d9d14</property>
<property name="template">test-fedora-25-clone</property>
<property name="vcpus">1</property>
</properties>
<features>
<feature name="service.meminfo-writer">1</feature>
<feature name="internal">1</feature>
</features>
<devices class="pci"/>
<devices class="block"/>
<tags/>
<volume-config>
<volume name="kernel" pool="linux-kernel" revisions_to_keep="0" vid="4.9.31-17"/>
<volume name="root" pool="default" revisions_to_keep="0" size="10737418240" snap_on_start="True" source="vm-templates/test-fedora-25-clone/root" vid="appvms/fedora-25-clone-dvm/root"/>
<volume name="volatile" pool="default" revisions_to_keep="0"
rw="True" size="10737418240" vid="appvms/fedora-25-clone-dvm/volatile"/>
<volume name="private" pool="default" revisions_to_keep="1" rw="True" save_on_stop="True" size="2147483648" vid="appvms/fedora-25-clone-dvm/private"/>
</volume-config>
</domain>
<domain id="domain-10" class="AppVM">
<properties>
<property name="dispvm_allowed">True</property>
<property name="label">label-1</property>
<property name="name">fedora-25-dvm</property>
<property name="qid">10</property>
<property name="uuid">30daa6b5-693b-460a-9da7-99078a2d9d14</property>
<property name="template">fedora-25</property>
<property name="vcpus">1</property>
</properties>
<features>
<feature name="service.meminfo-writer">1</feature>
<feature name="internal">1</feature>
</features>
<devices class="pci"/>
<devices class="block"/>
<tags/>
<volume-config>
<volume name="kernel" pool="linux-kernel" revisions_to_keep="0" vid="4.9.31-17"/>
<volume name="root" pool="default" revisions_to_keep="0" size="10737418240" snap_on_start="True" source="vm-templates/fedora-25/root" vid="appvms/fedora-25-dvm/root"/>
<volume name="volatile" pool="default" revisions_to_keep="0" rw="True" size="10737418240" vid="appvms/fedora-25-dvm/volatile"/>
<volume name="private" pool="default" revisions_to_keep="1" rw="True" save_on_stop="True" size="2147483648" vid="appvms/fedora-25-dvm/private"/>
</volume-config>
</domain>
<domain id="domain-14" class="TemplateVM">
<properties>
<property name="label">label-8</property>
<property name="maxmem">4000</property>
<property name="name">fedora-25-lvm</property>
<property name="qid">14</property>
<property name="uuid">20785bf4-42fa-4035-ab95-c1bf054c153a</property>
</properties>
<features/>
<devices class="pci"/>
<devices class="block"/>
<tags/>
<volume-config>
<volume name="kernel" pool="linux-kernel" revisions_to_keep="0" vid="4.9.31-17"/>
<volume name="root" pool="lvm" revisions_to_keep="1" rw="True" save_on_stop="True" size="10737418240" vid="core3/fedora-25-lvm-root"/>
<volume name="volatile" pool="lvm" revisions_to_keep="1" rw="True" size="10737418240" vid="core3/fedora-25-lvm-volatile"/>
<volume name="private" pool="lvm" revisions_to_keep="0" rw="True" save_on_stop="True" size="2147483648" vid="core3/fedora-25-lvm-private"/>
</volume-config>
</domain>
<domain id="domain-8" class="TemplateVM">
<properties>
<property name="label">label-8</property>
<property name="name">fedora-25</property>
<property name="qid">8</property>
<property name="uuid">51870d8e-2e71-41d2-9582-30661f611004</property>
</properties>
<features>
<feature name="qrexec">True</feature>
<feature name="updates-available"></feature>
<feature name="gui">1</feature>
</features>
<devices class="pci"/>
<devices class="block"/>
<tags>
<tag name="created-by-test-work"/>
</tags>
<volume-config>
<volume name="kernel" pool="linux-kernel" revisions_to_keep="0" vid="4.9.31-17"/>
<volume name="root" pool="default" revisions_to_keep="1" rw="True" save_on_stop="True" size="10737418240" vid="vm-templates/fedora-25/root"/>
<volume name="volatile" pool="default" revisions_to_keep="0" rw="True" size="10737418240" vid="vm-templates/fedora-25/volatile"/>
<volume name="private" pool="default" revisions_to_keep="0" rw="True" save_on_stop="True" size="2147483648" vid="vm-templates/fedora-25/private"/>
</volume-config>
</domain>
<domain id="domain-9" class="StandaloneVM">
<properties>
<property name="hvm">True</property>
<property name="label">label-7</property>
<property name="maxmem">4000</property>
<property name="name">test-hvm</property>
<property name="qid">9</property>
<property name="uuid">9909066b-0f03-4725-ad9e-fa3561d5566e</property>
</properties>
<features>
<feature name="service.meminfo-writer"></feature>
<feature name="backup-content">True</feature>
<feature name="backup-path">appvms/test-hvm</feature>
<feature name="backup-size">2097152000</feature>
</features>
<devices class="pci"/>
<devices class="block"/>
<tags/>
<volume-config>
<volume name="kernel" pool="linux-kernel" revisions_to_keep="0" vid="4.9.31-17"/>
<volume name="root" pool="default" revisions_to_keep="0" size="10737418240" vid="appvms/test-hvm/root"/>
<volume name="volatile" pool="default" revisions_to_keep="0" rw="True" size="10737418240" vid="appvms/test-hvm/volatile"/>
<volume name="private" pool="default" revisions_to_keep="1" rw="True" save_on_stop="True" size="2147483648" vid="appvms/test-hvm/private"/>
</volume-config>
</domain>
<domain id="domain-11" class="AppVM">
<properties>
<property name="backup_timestamp">1474318497</property>
<property name="label">label-1</property>
<property name="name">untrusted</property>
<property name="qid">11</property>
<property name="uuid">359b8e38-9e50-46a3-a42c-8d3bb15d3890</property>
<property name="default_dispvm">fedora-25-clone-dvm</property>
<property name="netvm"></property>
<property name="template">fedora-25</property>
</properties>
<features>
<feature name="service.meminfo-writer">1</feature>
</features>
<devices class="pci"/>
<devices class="block"/>
<tags/>
<volume-config>
<volume name="kernel" pool="linux-kernel" revisions_to_keep="0" vid="4.9.31-17"/>
<volume name="root" pool="default" revisions_to_keep="0" size="10737418240" snap_on_start="True" source="vm-templates/fedora-25/root" vid="appvms/untrusted/root"/>
<volume name="volatile" pool="lvm" revisions_to_keep="1" rw="True" size="10737418240" vid="core3/untrusted-volatile"/>
<volume name="private" pool="lvm" revisions_to_keep="1" rw="True" save_on_stop="True" size="2147483648" vid="core3/untrusted-private"/>
</volume-config>
</domain>
<domain id="domain-22" class="AppVM">
<properties>
<property name="label">label-3</property>
<property name="name">personal</property>
<property name="qid">22</property>
<property name="uuid">efa8ddc4-6661-4231-9bfd-ef34907da358</property>
<property name="netvm">sys-firewall</property>
<property name="template">fedora-25</property>
</properties>
<features>
<feature name="xxx">1</feature>
<feature name="feat1">1</feature>
<feature name="featdis"></feature>
<feature name="feat2"></feature>
<feature name="feat32">1</feature>
</features>
<devices class="pci"/>
<devices class="block"/>
<tags/>
<volume-config>
<volume name="kernel" pool="linux-kernel" revisions_to_keep="0" vid="4.9.31-17"/>
<volume name="root" pool="default" revisions_to_keep="0" size="10737418240" snap_on_start="True" source="vm-templates/fedora-25/root" vid="appvms/personal/root"/>
<volume name="volatile" pool="default" revisions_to_keep="0" rw="True" size="10737418240" vid="appvms/personal/volatile"/>
<volume name="private" pool="default" revisions_to_keep="1" rw="True" save_on_stop="True" size="2147483648" vid="appvms/personal/private"/>
</volume-config>
</domain>
<domain id="domain-21" class="AppVM">
<properties>
<property name="autostart">True</property>
<property name="label">label-4</property>
<property name="memory">500</property>
<property name="name">sys-firewall</property>
<property name="provides_network">True</property>
<property name="qid">21</property>
<property name="uuid">40f0775a-c259-44bc-be57-6148c67c42c7</property>
<property name="template">fedora-25</property>
</properties>
<features/>
<devices class="pci"/>
<devices class="block"/>
<devices class="usb"/>
<tags/>
<volume-config>
<volume name="kernel" pool="linux-kernel" revisions_to_keep="0" vid="4.9.31-17"/>
<volume name="root" pool="default" revisions_to_keep="0" size="10737418240" snap_on_start="True" source="vm-templates/fedora-25/root" vid="appvms/sys-firewall/root"/>
<volume name="volatile" pool="default" revisions_to_keep="0" rw="True" size="10737418240" vid="appvms/sys-firewall/volatile"/>
<volume name="private" pool="default" revisions_to_keep="1" rw="True" save_on_stop="True" size="2147483648" vid="appvms/sys-firewall/private"/>
</volume-config>
</domain>
<domain id="domain-2" class="AppVM">
<properties>
<property name="default_user">user</property>
<property name="hvm">False</property>
<property name="label">label-1</property>
<property name="maxmem">300</property>
<property name="memory">300</property>
<property name="name">sys-net</property>
<property name="netvm"></property>
<property name="provides_network">True</property>
<property name="kernelopts">nopat i8042.nokbd i8042.noaux</property>
<property name="qid">2</property>
<property name="uuid">eb8b1680-d4fa-449b-8baa-b5146d9b62b7</property>
<property name="template">fedora-25</property>
</properties>
<features>
<feature name="service.meminfo-writer"></feature>
<feature name="service.clocksync">1</feature>
</features>
<devices class="pci">
<device backend-domain="dom0" id="02_00.0"/>
</devices>
<devices class="block"/>
<devices class="usb"/>
<tags/>
<volume-config>
<volume name="kernel" pool="linux-kernel" revisions_to_keep="0" vid="4.9.31-17"/>
<volume name="root" pool="default" revisions_to_keep="0" size="10737418240" snap_on_start="True" source="vm-templates/fedora-25/root" vid="appvms/sys-net/root"/>
<volume name="volatile" pool="default" revisions_to_keep="0" rw="True" size="10737418240" vid="appvms/sys-net/volatile"/>
<volume name="private" pool="default" revisions_to_keep="1" rw="True" save_on_stop="True" size="2147483648" vid="appvms/sys-net/private"/>
</volume-config>
</domain>
<domain id="domain-6" class="AppVM">
<properties>
<property name="label">label-1</property>
<property name="maxmem">300</property>
<property name="name">test-net</property>
<property name="netvm"></property>
<property name="memory">300</property>
<property name="provides_network">True</property>
<property name="qid">6</property>
<property name="uuid">d5e9a792-9247-4f6f-a936-b7c65a1adfac</property>
<property name="template">fedora-25</property>
</properties>
<features>
<feature name="service.meminfo-writer"></feature>
<feature name="service.ntpd"></feature>
<feature name="backup-content">True</feature>
<feature name="backup-path">appvms/test-net</feature>
<feature name="backup-size">209715200</feature>
</features>
<devices class="pci">
<device backend-domain="dom0" id="03_00.0"/>
</devices>
<devices class="block"/>
<tags/>
<volume-config>
<volume name="kernel" pool="linux-kernel" revisions_to_keep="0" vid="4.9.31-17"/>
<volume name="root" pool="default" revisions_to_keep="0" size="10737418240" snap_on_start="True" source="vm-templates/fedora-25/root" vid="appvms/test-net/root"/>
<volume name="volatile" pool="default" revisions_to_keep="0" rw="True" size="10737418240" vid="appvms/test-net/volatile"/>
<volume name="private" pool="default" revisions_to_keep="1" rw="True" save_on_stop="True" size="2147483648" vid="appvms/test-net/private"/>
</volume-config>
</domain>
<domain id="domain-5" class="AppVM">
<properties>
<property name="autostart">True</property>
<property name="label">label-1</property>
<property name="hvm">False</property>
<property name="maxmem">400</property>
<property name="name">sys-usb</property>
<property name="qid">5</property>
<property name="uuid">0da5616e-f2db-4c17-9c0d-cdef2e728344</property>
<property name="template">fedora-25</property>
<property name="provides_network">True</property>
</properties>
<features>
<feature name="service.network-manager"></feature>
<feature name="service.meminfo-writer"></feature>
</features>
<devices class="pci">
<device backend-domain="dom0" id="00_14.0">
<option name="no-strict-reset">True</option>
</device>
<device backend-domain="dom0" id="00_1d.0">
<option name="no-strict-reset">True</option>
</device>
</devices>
<devices class="block"/>
<tags/>
<volume-config>
<volume name="kernel" pool="linux-kernel" revisions_to_keep="0" vid="4.9.31-17"/>
<volume name="root" pool="default" revisions_to_keep="0" size="10737418240" snap_on_start="True" source="vm-templates/fedora-25/root" vid="appvms/sys-usb/root"/>
<volume name="volatile" pool="default" revisions_to_keep="0" rw="True" size="10737418240" vid="appvms/sys-usb/volatile"/>
<volume name="private" pool="default" revisions_to_keep="1" rw="True" save_on_stop="True" size="2147483648" vid="appvms/sys-usb/private"/>
</volume-config>
</domain>
<domain id="domain-13" class="AppVM">
<properties>
<property name="label">label-8</property>
<property name="name">vault</property>
<property name="netvm"></property>
<property name="qid">13</property>
<property name="uuid">d5284828-988d-46e2-8388-a09c495475e3</property>
<property name="template">fedora-25</property>
<property name="hvm">False</property>
<property name="maxmem">1536</property>
</properties>
<features/>
<devices class="pci"/>
<devices class="block"/>
<tags/>
<volume-config>
<volume name="kernel" pool="linux-kernel" revisions_to_keep="0" vid="4.9.31-17"/>
<volume name="root" pool="default" revisions_to_keep="0" size="10737418240" snap_on_start="True" source="vm-templates/fedora-25/root" vid="appvms/vault/root"/>
<volume name="volatile" pool="lvm" revisions_to_keep="1" rw="True" size="10737418240" vid="core3/vault-volatile"/>
<volume name="private" pool="lvm" revisions_to_keep="1" rw="True" save_on_stop="True" size="2147483648" vid="core3/vault-private"/>
</volume-config>
</domain>
<domain id="domain-3" class="AppVM">
<properties>
<property name="ip">192.168.0.1</property>
<property name="label">label-4</property>
<property name="maxmem">4000</property>
<property name="memory">400</property>
<property name="name">test-work</property>
<property name="qid">3</property>
<property name="uuid">07c17d1e-0982-417d-b19e-a81d51bed423</property>
<property name="template">fedora-25</property>
</properties>
<features>
<feature name="service.meminfo-writer">1</feature>
<feature name="backup-content">True</feature>
<feature name="backup-path">appvms/test-work</feature>
<feature name="backup-size">2097152000</feature>
</features>
<devices class="pci"/>
<devices class="block"/>
<tags>
<tag name="tag1"/>
<tag name="tag2"/>
</tags>
<volume-config>
<volume name="kernel" pool="linux-kernel" revisions_to_keep="0" vid="4.9.31-17"/>
<volume name="root" pool="default" revisions_to_keep="0" size="10737418240" snap_on_start="True" source="vm-templates/fedora-25/root" vid="appvms/test-work/root"/>
<volume name="volatile" pool="default" revisions_to_keep="0" rw="True" size="10737418240" vid="appvms/test-work/volatile"/>
<volume name="private" pool="default" revisions_to_keep="1" rw="True" save_on_stop="True" size="2147483648" vid="appvms/test-work/private"/>
</volume-config>
</domain>
<domain id="domain-4" class="StandaloneVM">
<properties>
<property name="label">label-6</property>
<property name="maxmem">4000</property>
<property name="name">test-standalonevm</property>
<property name="qid">4</property>
<property name="uuid">e8034b8a-29b3-4f02-b8cb-05cd74a4bb68</property>
</properties>
<features>
<feature name="backup-content">True</feature>
<feature name="backup-path">appvms/test-standalonevm</feature>
<feature name="backup-size">2097152000</feature>
</features>
<tags/>
<volume-config>
<volume name="root" pool="default" revisions_to_keep="1" rw="True" save_on_stop="True" size="10737418240" vid="appvms/test-standalonevm/root"/>
<volume name="private" pool="default" revisions_to_keep="1" rw="True" save_on_stop="True" size="2147483648" vid="appvms/test-standalonevm/private"/>
<volume name="volatile" pool="default" revisions_to_keep="0" rw="True" size="10737418240" vid="appvms/test-standalonevm/volatile"/>
<volume name="kernel" pool="linux-kernel" revisions_to_keep="0" vid="None"/>
</volume-config>
</domain>
</domains>
</qubes>

View File

@ -129,7 +129,7 @@ class TC_00_DeviceCollection(qubesadmin.tests.QubesTestCase):
def test_022_attach_persistent(self):
self.app.expected_calls[
('test-vm', 'admin.vm.device.test.Attach', 'test-vm2+dev1',
b'persistent=yes')] = b'0\0'
b'persistent=True')] = b'0\0'
assign = qubesadmin.devices.DeviceAssignment(
self.app.domains['test-vm2'], 'dev1')
assign.persistent = True
@ -139,7 +139,7 @@ class TC_00_DeviceCollection(qubesadmin.tests.QubesTestCase):
def test_023_attach_persistent_options(self):
self.app.expected_calls[
('test-vm', 'admin.vm.device.test.Attach', 'test-vm2+dev1',
b'persistent=yes ro=True')] = b'0\0'
b'persistent=True ro=True')] = b'0\0'
assign = qubesadmin.devices.DeviceAssignment(
self.app.domains['test-vm2'], 'dev1')
assign.persistent = True

View File

@ -87,7 +87,7 @@ class TC_00_qvm_device(qubesadmin.tests.QubesTestCase):
None, None)] = b'0\0'
self.app.expected_calls[('test-vm3', 'admin.vm.device.test.List',
None, None)] = \
b'0\0test-vm1+dev1 persistent=yes\n'
b'0\0test-vm1+dev1 persistent=True\n'
with qubesadmin.tests.tools.StdoutBuffer() as buf:
qubesadmin.tools.qvm_device.main(
@ -144,7 +144,7 @@ class TC_00_qvm_device(qubesadmin.tests.QubesTestCase):
def test_011_attach_persistent(self):
''' Test attach action '''
self.app.expected_calls[('test-vm2', 'admin.vm.device.test.Attach',
'test-vm1+dev1', b'persistent=yes')] = b'0\0'
'test-vm1+dev1', b'persistent=True')] = b'0\0'
qubesadmin.tools.qvm_device.main(
['test', 'attach', '-p', 'test-vm2', 'test-vm1:dev1'],
app=self.app)

View File

@ -0,0 +1,264 @@
#
# The Qubes OS Project, http://www.qubes-os.org
#
# Copyright (C) 2016 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 Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser 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.
'''Console frontend for backup restore code'''
import getpass
import sys
import qubesadmin.backup
import qubesadmin.exc
import qubesadmin.tools
import qubesadmin.utils
parser = qubesadmin.tools.QubesArgumentParser()
parser.add_argument("--verify-only", action="store_true",
dest="verify_only", default=False,
help="Verify backup integrity without restoring any "
"data")
parser.add_argument("--skip-broken", action="store_true", dest="skip_broken",
default=False,
help="Do not restore VMs that have missing TemplateVMs "
"or NetVMs")
parser.add_argument("--ignore-missing", action="store_true",
dest="ignore_missing", default=False,
help="Restore VMs even if their associated TemplateVMs "
"and NetVMs are missing")
parser.add_argument("--skip-conflicting", action="store_true",
dest="skip_conflicting", default=False,
help="Do not restore VMs that are already present on "
"the host")
parser.add_argument("--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_argument("--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_argument("-x", "--exclude", action="append", dest="exclude",
default=[],
help="Skip restore of specified VM (may be repeated)")
parser.add_argument("--skip-dom0-home", action="store_false", dest="dom0_home",
default=True,
help="Do not restore dom0 user home directory")
parser.add_argument("--ignore-username-mismatch", action="store_true",
dest="ignore_username_mismatch", default=False,
help="Ignore dom0 username mismatch when restoring home "
"directory")
parser.add_argument("-d", "--dest-vm", action="store", dest="appvm",
help="Specify VM containing the backup to be restored")
parser.add_argument("-p", "--passphrase-file", action="store",
dest="pass_file", default=None,
help="Read passphrase from file, or use '-' to read from stdin")
parser.add_argument('backup_location', action='store',
help="Backup directory name, or command to pipe from")
parser.add_argument('vms', nargs='*', action='store', default='[]',
help='Restore only those VMs')
def handle_broken(app, args, restore_info):
'''Display information about problems with VMs selected for resetore'''
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():
assert isinstance(vm_info, qubesadmin.backup.BackupRestore.VMToRestore)
if qubesadmin.backup.BackupRestore.VMToRestore.EXCLUDED in \
vm_info.problems:
continue
if qubesadmin.backup.BackupRestore.VMToRestore.MISSING_TEMPLATE in \
vm_info.problems:
there_are_missing_templates = True
if qubesadmin.backup.BackupRestore.VMToRestore.MISSING_NETVM in \
vm_info.problems:
there_are_missing_netvms = True
if qubesadmin.backup.BackupRestore.VMToRestore.ALREADY_EXISTS in \
vm_info.problems:
there_are_conflicting_vms = True
if qubesadmin.backup.BackupRestore.Dom0ToRestore.USERNAME_MISMATCH in \
vm_info.problems:
dom0_username_mismatch = True
if there_are_conflicting_vms:
app.log.error(
"*** There are VMs with conflicting names on the host! ***")
if args.skip_conflicting:
app.log.error(
"Those VMs will not be restored. "
"The host VMs will NOT be overwritten.")
else:
raise qubesadmin.exc.QubesException(
"Remove VMs with conflicting names from the host "
"before proceeding.\n"
"Or use --skip-conflicting to restore only those VMs that "
"do not exist on the host.\n"
"Or use --rename-conflicting to restore those VMs under "
"modified names (with numbers at the end).")
app.log.info("The above VMs will be copied and added to your system.")
app.log.info("Exisiting VMs will NOT be removed.")
if there_are_missing_templates:
app.log.warning("*** One or more TemplateVMs are missing on the "
"host! ***")
if not (args.skip_broken or args.ignore_missing):
raise qubesadmin.exc.QubesException(
"Install them before proceeding with the restore."
"Or pass: --skip-broken or --ignore-missing.")
elif args.skip_broken:
app.log.warning("Skipping broken entries: VMs that depend on "
"missing TemplateVMs will NOT be restored.")
elif args.ignore_missing:
app.log.warning("Ignoring missing entries: VMs that depend "
"on missing TemplateVMs will NOT be restored.")
else:
raise qubesadmin.exc.QubesException(
"INTERNAL ERROR! Please report this to the Qubes OS team!")
if there_are_missing_netvms:
app.log.warning("*** One or more NetVMs are missing on the "
"host! ***")
if not (args.skip_broken or args.ignore_missing):
raise qubesadmin.exc.QubesException(
"Install them before proceeding with the restore."
"Or pass: --skip-broken or --ignore-missing.")
elif args.skip_broken:
app.log.warning("Skipping broken entries: VMs that depend on "
"missing NetVMs will NOT be restored.")
elif args.ignore_missing:
app.log.warning("Ignoring missing entries: VMs that depend "
"on missing NetVMs will NOT be restored.")
else:
raise qubesadmin.exc.QubesException(
"INTERNAL ERROR! Please report this to the Qubes OS team!")
if 'dom0' in restore_info.keys() and args.dom0_home:
if dom0_username_mismatch:
app.log.warning("*** Dom0 username mismatch! This can break "
"some settings! ***")
if not args.ignore_username_mismatch:
raise qubesadmin.exc.QubesException(
"Skip restoring the dom0 home directory "
"(--skip-dom0-home), or pass "
"--ignore-username-mismatch to continue anyway.")
else:
app.log.warning("Continuing as directed.")
app.log.warning("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.")
def main(args=None):
'''Main function of qvm-backup-restore'''
# pylint: disable=too-many-return-statements
args = parser.parse_args(args)
appvm = None
if args.appvm:
try:
appvm = args.app.domains[args.appvm]
except KeyError:
parser.error('no such domain: {!r}'.format(args.appvm))
if args.pass_file is not None:
pass_f = open(args.pass_file) if args.pass_file != "-" else sys.stdin
passphrase = pass_f.readline().rstrip()
if pass_f is not sys.stdin:
pass_f.close()
else:
passphrase = getpass.getpass("Please enter the passphrase to verify "
"and (if encrypted) decrypt the backup: ")
args.app.log.info("Checking backup content...")
try:
backup = qubesadmin.backup.BackupRestore(args.app, args.backup_location,
appvm, passphrase)
except qubesadmin.exc.QubesException as e:
parser.error_runtime(str(e))
# unreachable - error_runtime will raise SystemExit
return 1
if args.ignore_missing:
backup.options.use_default_template = True
backup.options.use_default_netvm = True
if args.replace_template:
backup.options.replace_template = args.replace_template
if args.rename_conflicting:
backup.options.rename_conflicting = True
if not args.dom0_home:
backup.options.dom0_home = False
if args.ignore_username_mismatch:
backup.options.ignore_username_mismatch = True
if args.exclude:
backup.options.exclude = args.exclude
if args.verify_only:
backup.options.verify_only = True
restore_info = None
try:
restore_info = backup.get_restore_info()
except qubesadmin.exc.QubesException as e:
parser.error_runtime(str(e))
if args.vms:
backup.options.exclude += [vm for vm in restore_info
if vm not in args.vms]
restore_info = backup.restore_info_verify(restore_info)
print(backup.get_restore_summary(restore_info))
try:
handle_broken(args.app, args, restore_info)
except qubesadmin.exc.QubesException as e:
parser.error_runtime(str(e))
if args.pass_file is None:
if input("Do you want to proceed? [y/N] ").upper() != "Y":
exit(0)
try:
backup.restore_do(restore_info)
except qubesadmin.exc.QubesException as e:
parser.error_runtime(str(e))
if __name__ == '__main__':
main()

View File

@ -7,6 +7,7 @@ import sys
exclude=[]
if sys.version_info[0:2] < (3, 4):
exclude += ['qubesadmin.tools', 'qubesadmin.tests.tools']
exclude += ['qubesadmin.backup', 'qubesadmin.tests.backup']
if sys.version_info[0:2] < (3, 5):
exclude += ['qubesadmin.events']
@ -32,6 +33,9 @@ if __name__ == '__main__':
license='LGPL2.1+',
url='https://www.qubes-os.org/',
packages=setuptools.find_packages(exclude=exclude),
package_data={
'qubesadmin.tests.backup': ['*.xml'],
},
entry_points={
'console_scripts': list(get_console_scripts()),
'qubesadmin.vm': [