backup: initial conversion to core3 API
This commit is contained in:
parent
45d6ab3862
commit
e0686e1e02
@ -23,10 +23,7 @@
|
|||||||
#
|
#
|
||||||
#
|
#
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
from qubes import QubesException, QubesVmCollection
|
from qubes.utils import size_to_human
|
||||||
from qubes import QubesVmClasses
|
|
||||||
from qubes import system_path, vm_files
|
|
||||||
from qubesutils import size_to_human, print_stdout, print_stderr, get_disk_usage
|
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
import fcntl
|
import fcntl
|
||||||
@ -40,6 +37,7 @@ import pwd
|
|||||||
import errno
|
import errno
|
||||||
import datetime
|
import datetime
|
||||||
from multiprocessing import Queue, Process
|
from multiprocessing import Queue, Process
|
||||||
|
import qubes
|
||||||
|
|
||||||
BACKUP_DEBUG = False
|
BACKUP_DEBUG = False
|
||||||
|
|
||||||
@ -53,9 +51,37 @@ MAX_STDERR_BYTES = 1024
|
|||||||
# header + qubes.xml max size
|
# header + qubes.xml max size
|
||||||
HEADER_QUBES_XML_MAX_SIZE = 1024 * 1024
|
HEADER_QUBES_XML_MAX_SIZE = 1024 * 1024
|
||||||
|
|
||||||
|
BLKSIZE = 512
|
||||||
|
|
||||||
# global state for backup_cancel()
|
# global state for backup_cancel()
|
||||||
running_backup_operation = None
|
running_backup_operation = None
|
||||||
|
|
||||||
|
def print_stdout(text):
|
||||||
|
print (text)
|
||||||
|
|
||||||
|
def print_stderr(text):
|
||||||
|
print >> sys.stderr, (text)
|
||||||
|
|
||||||
|
def get_disk_usage_one(st):
|
||||||
|
try:
|
||||||
|
return st.st_blocks * BLKSIZE
|
||||||
|
except AttributeError:
|
||||||
|
return st.st_size
|
||||||
|
|
||||||
|
def get_disk_usage(path):
|
||||||
|
try:
|
||||||
|
st = os.lstat(path)
|
||||||
|
except OSError:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
ret = get_disk_usage_one(st)
|
||||||
|
|
||||||
|
# if path is not a directory, this is skipped
|
||||||
|
for dirpath, dirnames, filenames in os.walk(path):
|
||||||
|
for name in dirnames + filenames:
|
||||||
|
ret += get_disk_usage_one(os.lstat(os.path.join(dirpath, name)))
|
||||||
|
|
||||||
|
return ret
|
||||||
|
|
||||||
class BackupOperationInfo:
|
class BackupOperationInfo:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@ -64,7 +90,7 @@ class BackupOperationInfo:
|
|||||||
self.tmpdir_to_remove = None
|
self.tmpdir_to_remove = None
|
||||||
|
|
||||||
|
|
||||||
class BackupCanceledError(QubesException):
|
class BackupCanceledError(qubes.exc.QubesException):
|
||||||
def __init__(self, msg, tmpdir=None):
|
def __init__(self, msg, tmpdir=None):
|
||||||
super(BackupCanceledError, self).__init__(msg)
|
super(BackupCanceledError, self).__init__(msg)
|
||||||
self.tmpdir = tmpdir
|
self.tmpdir = tmpdir
|
||||||
@ -86,7 +112,7 @@ def file_to_backup(file_path, subdir=None):
|
|||||||
|
|
||||||
if subdir is None:
|
if subdir is None:
|
||||||
abs_file_path = os.path.abspath(file_path)
|
abs_file_path = os.path.abspath(file_path)
|
||||||
abs_base_dir = os.path.abspath(system_path["qubes_base_dir"]) + '/'
|
abs_base_dir = os.path.abspath(qubes.config.system_path["qubes_base_dir"]) + '/'
|
||||||
abs_file_dir = os.path.dirname(abs_file_path) + '/'
|
abs_file_dir = os.path.dirname(abs_file_path) + '/'
|
||||||
(nothing, directory, subdir) = abs_file_dir.partition(abs_base_dir)
|
(nothing, directory, subdir) = abs_file_dir.partition(abs_base_dir)
|
||||||
assert nothing == ""
|
assert nothing == ""
|
||||||
@ -115,33 +141,35 @@ def backup_cancel():
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def backup_prepare(vms_list=None, exclude_list=None,
|
def backup_prepare(app, vms_list=None, exclude_list=None,
|
||||||
print_callback=print_stdout, hide_vm_names=True):
|
print_callback=print_stdout, hide_vm_names=True):
|
||||||
"""
|
"""
|
||||||
If vms = None, include all (sensible) VMs;
|
If vms = None, include all (sensible) VMs;
|
||||||
exclude_list is always applied
|
exclude_list is always applied
|
||||||
"""
|
"""
|
||||||
files_to_backup = file_to_backup(system_path["qubes_store_filename"])
|
# XXX hack for tests, where store filename is qubes-test.xml
|
||||||
|
qubes_xml = app._store
|
||||||
|
if os.path.basename(qubes_xml) != 'qubes.xml':
|
||||||
|
dir = tempfile.mkdtemp()
|
||||||
|
shutil.copy(qubes_xml, os.path.join(dir, 'qubes.xml'))
|
||||||
|
qubes_xml = os.path.join(dir, 'qubes.xml')
|
||||||
|
# FIXME cleanup tempdir later
|
||||||
|
|
||||||
|
files_to_backup = file_to_backup(qubes_xml, '')
|
||||||
|
|
||||||
if exclude_list is None:
|
if exclude_list is None:
|
||||||
exclude_list = []
|
exclude_list = []
|
||||||
|
|
||||||
qvm_collection = QubesVmCollection()
|
|
||||||
qvm_collection.lock_db_for_writing()
|
|
||||||
qvm_collection.load()
|
|
||||||
|
|
||||||
if vms_list is None:
|
if vms_list is None:
|
||||||
all_vms = [vm for vm in qvm_collection.values()]
|
all_vms = [vm for vm in app.domains]
|
||||||
selected_vms = [vm for vm in all_vms if vm.include_in_backups]
|
selected_vms = [vm for vm in all_vms if vm.include_in_backups]
|
||||||
appvms_to_backup = [vm for vm in selected_vms if
|
appvms_to_backup = [vm for vm in selected_vms if
|
||||||
vm.is_appvm() and not vm.internal]
|
vm.is_appvm() and not vm.internal]
|
||||||
netvms_to_backup = [vm for vm in selected_vms if
|
|
||||||
vm.is_netvm() and not vm.qid == 0]
|
|
||||||
template_vms_worth_backingup = [vm for vm in selected_vms if (
|
template_vms_worth_backingup = [vm for vm in selected_vms if (
|
||||||
vm.is_template() and vm.include_in_backups)]
|
vm.is_template() and vm.include_in_backups)]
|
||||||
dom0 = [qvm_collection[0]]
|
dom0 = [app.domains[0]]
|
||||||
|
|
||||||
vms_list = appvms_to_backup + netvms_to_backup + \
|
vms_list = appvms_to_backup + \
|
||||||
template_vms_worth_backingup + dom0
|
template_vms_worth_backingup + dom0
|
||||||
|
|
||||||
vms_for_backup = vms_list
|
vms_for_backup = vms_list
|
||||||
@ -207,11 +235,13 @@ def backup_prepare(vms_list=None, exclude_list=None,
|
|||||||
subdir)
|
subdir)
|
||||||
if os.path.exists(vm.firewall_conf):
|
if os.path.exists(vm.firewall_conf):
|
||||||
files_to_backup += file_to_backup(vm.firewall_conf, subdir)
|
files_to_backup += file_to_backup(vm.firewall_conf, subdir)
|
||||||
if 'appmenus_whitelist' in vm_files and \
|
if 'appmenus_whitelist' in qubes.config.vm_files and \
|
||||||
os.path.exists(os.path.join(vm.dir_path,
|
os.path.exists(os.path.join(vm.dir_path,
|
||||||
vm_files['appmenus_whitelist'])):
|
qubes.config.vm_files[
|
||||||
|
'appmenus_whitelist'])):
|
||||||
files_to_backup += file_to_backup(
|
files_to_backup += file_to_backup(
|
||||||
os.path.join(vm.dir_path, vm_files['appmenus_whitelist']),
|
os.path.join(vm.dir_path, qubes.config.vm_files[
|
||||||
|
'appmenus_whitelist']),
|
||||||
subdir)
|
subdir)
|
||||||
|
|
||||||
if vm.updateable:
|
if vm.updateable:
|
||||||
@ -255,7 +285,7 @@ def backup_prepare(vms_list=None, exclude_list=None,
|
|||||||
else:
|
else:
|
||||||
template_subdir = os.path.relpath(
|
template_subdir = os.path.relpath(
|
||||||
vm.dir_path,
|
vm.dir_path,
|
||||||
system_path["qubes_base_dir"]) + '/'
|
qubes.config.system_path["qubes_base_dir"]) + '/'
|
||||||
template_to_backup = [{"path": vm.dir_path + '/.',
|
template_to_backup = [{"path": vm.dir_path + '/.',
|
||||||
"size": vm_sz,
|
"size": vm_sz,
|
||||||
"subdir": template_subdir}]
|
"subdir": template_subdir}]
|
||||||
@ -280,7 +310,7 @@ def backup_prepare(vms_list=None, exclude_list=None,
|
|||||||
|
|
||||||
# Initialize backup flag on all VMs
|
# Initialize backup flag on all VMs
|
||||||
vms_for_backup_qid = [vm.qid for vm in vms_for_backup]
|
vms_for_backup_qid = [vm.qid for vm in vms_for_backup]
|
||||||
for vm in qvm_collection.values():
|
for vm in app.domains:
|
||||||
vm.backup_content = False
|
vm.backup_content = False
|
||||||
if vm.qid == 0:
|
if vm.qid == 0:
|
||||||
# handle dom0 later
|
# handle dom0 later
|
||||||
@ -292,8 +322,9 @@ def backup_prepare(vms_list=None, exclude_list=None,
|
|||||||
if hide_vm_names:
|
if hide_vm_names:
|
||||||
vm.backup_path = 'vm%d' % vm.qid
|
vm.backup_path = 'vm%d' % vm.qid
|
||||||
else:
|
else:
|
||||||
vm.backup_path = os.path.relpath(vm.dir_path,
|
vm.backup_path = os.path.relpath(
|
||||||
system_path["qubes_base_dir"])
|
vm.dir_path,
|
||||||
|
qubes.config.system_path["qubes_base_dir"])
|
||||||
|
|
||||||
# Dom0 user home
|
# Dom0 user home
|
||||||
if 0 in vms_for_backup_qid:
|
if 0 in vms_for_backup_qid:
|
||||||
@ -309,7 +340,7 @@ def backup_prepare(vms_list=None, exclude_list=None,
|
|||||||
{"path": home_dir, "size": home_sz, "subdir": 'dom0-home/'}]
|
{"path": home_dir, "size": home_sz, "subdir": 'dom0-home/'}]
|
||||||
files_to_backup += home_to_backup
|
files_to_backup += home_to_backup
|
||||||
|
|
||||||
vm = qvm_collection[0]
|
vm = app.domains[0]
|
||||||
vm.backup_content = True
|
vm.backup_content = True
|
||||||
vm.backup_size = home_sz
|
vm.backup_size = home_sz
|
||||||
vm.backup_path = os.path.join('dom0-home', os.path.basename(home_dir))
|
vm.backup_path = os.path.join('dom0-home', os.path.basename(home_dir))
|
||||||
@ -326,9 +357,8 @@ def backup_prepare(vms_list=None, exclude_list=None,
|
|||||||
|
|
||||||
print_callback(s)
|
print_callback(s)
|
||||||
|
|
||||||
qvm_collection.save()
|
app.save()
|
||||||
# FIXME: should be after backup completed
|
# FIXME: should be after backup completed
|
||||||
qvm_collection.unlock_db()
|
|
||||||
|
|
||||||
total_backup_sz = 0
|
total_backup_sz = 0
|
||||||
for f in files_to_backup:
|
for f in files_to_backup:
|
||||||
@ -355,13 +385,13 @@ def backup_prepare(vms_list=None, exclude_list=None,
|
|||||||
s += fmt.format('-')
|
s += fmt.format('-')
|
||||||
print_callback(s)
|
print_callback(s)
|
||||||
|
|
||||||
vms_not_for_backup = [vm.name for vm in qvm_collection.values()
|
vms_not_for_backup = [vm.name for vm in app.domains
|
||||||
if not vm.backup_content]
|
if not vm.backup_content]
|
||||||
print_callback("VMs not selected for backup: %s" % " ".join(
|
print_callback("VMs not selected for backup: %s" % " ".join(
|
||||||
vms_not_for_backup))
|
vms_not_for_backup))
|
||||||
|
|
||||||
if there_are_running_vms:
|
if there_are_running_vms:
|
||||||
raise QubesException("Please shutdown all VMs before proceeding.")
|
raise qubes.exc.QubesException("Please shutdown all VMs before proceeding.")
|
||||||
|
|
||||||
for fileinfo in files_to_backup:
|
for fileinfo in files_to_backup:
|
||||||
assert len(fileinfo["subdir"]) == 0 or fileinfo["subdir"][-1] == '/', \
|
assert len(fileinfo["subdir"]) == 0 or fileinfo["subdir"][-1] == '/', \
|
||||||
@ -406,7 +436,7 @@ class SendWorker(Process):
|
|||||||
self.queue.get()
|
self.queue.get()
|
||||||
# handle only exit code 2 (tar fatal error) or
|
# handle only exit code 2 (tar fatal error) or
|
||||||
# greater (call failed?)
|
# greater (call failed?)
|
||||||
raise QubesException(
|
raise qubes.exc.QubesException(
|
||||||
"ERROR: Failed to write the backup, out of disk space? "
|
"ERROR: Failed to write the backup, out of disk space? "
|
||||||
"Check console output or ~/.xsession-errors for details.")
|
"Check console output or ~/.xsession-errors for details.")
|
||||||
|
|
||||||
@ -442,11 +472,11 @@ def prepare_backup_header(target_directory, passphrase, compressed=False,
|
|||||||
stdin=open(header_file_path, "r"),
|
stdin=open(header_file_path, "r"),
|
||||||
stdout=open(header_file_path + ".hmac", "w"))
|
stdout=open(header_file_path + ".hmac", "w"))
|
||||||
if hmac.wait() != 0:
|
if hmac.wait() != 0:
|
||||||
raise QubesException("Failed to compute hmac of header file")
|
raise qubes.exc.QubesException("Failed to compute hmac of header file")
|
||||||
return HEADER_FILENAME, HEADER_FILENAME + ".hmac"
|
return HEADER_FILENAME, HEADER_FILENAME + ".hmac"
|
||||||
|
|
||||||
|
|
||||||
def backup_do(base_backup_dir, files_to_backup, passphrase,
|
def backup_do(app, base_backup_dir, files_to_backup, passphrase,
|
||||||
progress_callback=None, encrypted=False, appvm=None,
|
progress_callback=None, encrypted=False, appvm=None,
|
||||||
compressed=False, hmac_algorithm=DEFAULT_HMAC_ALGORITHM,
|
compressed=False, hmac_algorithm=DEFAULT_HMAC_ALGORITHM,
|
||||||
crypto_algorithm=DEFAULT_CRYPTO_ALGORITHM):
|
crypto_algorithm=DEFAULT_CRYPTO_ALGORITHM):
|
||||||
@ -460,7 +490,7 @@ def backup_do(base_backup_dir, files_to_backup, passphrase,
|
|||||||
vmproc.stderr.read())
|
vmproc.stderr.read())
|
||||||
else:
|
else:
|
||||||
message = "Failed to write the backup. Out of disk space?"
|
message = "Failed to write the backup. Out of disk space?"
|
||||||
raise QubesException(message)
|
raise qubes.exc.QubesException(message)
|
||||||
queue.put(element)
|
queue.put(element)
|
||||||
|
|
||||||
total_backup_sz = 0
|
total_backup_sz = 0
|
||||||
@ -497,7 +527,7 @@ def backup_do(base_backup_dir, files_to_backup, passphrase,
|
|||||||
|
|
||||||
# Create the target directory
|
# Create the target directory
|
||||||
if not os.path.exists(os.path.dirname(base_backup_dir)):
|
if not os.path.exists(os.path.dirname(base_backup_dir)):
|
||||||
raise QubesException(
|
raise qubes.exc.QubesException(
|
||||||
"ERROR: the backup directory for {0} does not exists".
|
"ERROR: the backup directory for {0} does not exists".
|
||||||
format(base_backup_dir))
|
format(base_backup_dir))
|
||||||
|
|
||||||
@ -658,11 +688,11 @@ def backup_do(base_backup_dir, files_to_backup, passphrase,
|
|||||||
if run_error and run_error != "size_limit":
|
if run_error and run_error != "size_limit":
|
||||||
send_proc.terminate()
|
send_proc.terminate()
|
||||||
if run_error == "VM" and vmproc:
|
if run_error == "VM" and vmproc:
|
||||||
raise QubesException(
|
raise qubes.exc.QubesException(
|
||||||
"Failed to write the backup, VM output:\n" +
|
"Failed to write the backup, VM output:\n" +
|
||||||
vmproc.stderr.read(MAX_STDERR_BYTES))
|
vmproc.stderr.read(MAX_STDERR_BYTES))
|
||||||
else:
|
else:
|
||||||
raise QubesException("Failed to perform backup: error in " +
|
raise qubes.exc.QubesException("Failed to perform backup: error in " +
|
||||||
run_error)
|
run_error)
|
||||||
|
|
||||||
# Send the chunk to the backup target
|
# Send the chunk to the backup target
|
||||||
@ -711,7 +741,7 @@ def backup_do(base_backup_dir, files_to_backup, passphrase,
|
|||||||
running_backup_operation = None
|
running_backup_operation = None
|
||||||
|
|
||||||
if send_proc.exitcode != 0:
|
if send_proc.exitcode != 0:
|
||||||
raise QubesException(
|
raise qubes.exc.QubesException(
|
||||||
"Failed to send backup: error in the sending process")
|
"Failed to send backup: error in the sending process")
|
||||||
|
|
||||||
if vmproc:
|
if vmproc:
|
||||||
@ -722,16 +752,11 @@ def backup_do(base_backup_dir, files_to_backup, passphrase,
|
|||||||
vmproc.stdin.close()
|
vmproc.stdin.close()
|
||||||
|
|
||||||
# Save date of last backup
|
# Save date of last backup
|
||||||
qvm_collection = QubesVmCollection()
|
for vm in app.domains:
|
||||||
qvm_collection.lock_db_for_writing()
|
|
||||||
qvm_collection.load()
|
|
||||||
|
|
||||||
for vm in qvm_collection.values():
|
|
||||||
if vm.backup_content:
|
if vm.backup_content:
|
||||||
vm.backup_timestamp = datetime.datetime.now()
|
vm.backup_timestamp = datetime.datetime.now()
|
||||||
|
|
||||||
qvm_collection.save()
|
app.save()
|
||||||
qvm_collection.unlock_db()
|
|
||||||
|
|
||||||
|
|
||||||
'''
|
'''
|
||||||
@ -829,7 +854,7 @@ def verify_hmac(filename, hmacfile, passphrase, algorithm):
|
|||||||
print "Verifying file " + filename
|
print "Verifying file " + filename
|
||||||
|
|
||||||
if hmacfile != filename + ".hmac":
|
if hmacfile != filename + ".hmac":
|
||||||
raise QubesException(
|
raise qubes.exc.QubesException(
|
||||||
"ERROR: expected hmac for {}, but got {}".
|
"ERROR: expected hmac for {}, but got {}".
|
||||||
format(filename, hmacfile))
|
format(filename, hmacfile))
|
||||||
|
|
||||||
@ -840,7 +865,7 @@ def verify_hmac(filename, hmacfile, passphrase, algorithm):
|
|||||||
hmac_stdout, hmac_stderr = hmac_proc.communicate()
|
hmac_stdout, hmac_stderr = hmac_proc.communicate()
|
||||||
|
|
||||||
if len(hmac_stderr) > 0:
|
if len(hmac_stderr) > 0:
|
||||||
raise QubesException(
|
raise qubes.exc.QubesException(
|
||||||
"ERROR: verify file {0}: {1}".format(filename, hmac_stderr))
|
"ERROR: verify file {0}: {1}".format(filename, hmac_stderr))
|
||||||
else:
|
else:
|
||||||
if BACKUP_DEBUG:
|
if BACKUP_DEBUG:
|
||||||
@ -853,7 +878,7 @@ def verify_hmac(filename, hmacfile, passphrase, algorithm):
|
|||||||
print "File verification OK -> Sending file " + filename
|
print "File verification OK -> Sending file " + filename
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
raise QubesException(
|
raise qubes.exc.QubesException(
|
||||||
"ERROR: invalid hmac for file {0}: {1}. "
|
"ERROR: invalid hmac for file {0}: {1}. "
|
||||||
"Is the passphrase correct?".
|
"Is the passphrase correct?".
|
||||||
format(filename, load_hmac(hmac_stdout)))
|
format(filename, load_hmac(hmac_stdout)))
|
||||||
@ -1081,7 +1106,7 @@ class ExtractWorker2(Process):
|
|||||||
self.tar2_process.wait()
|
self.tar2_process.wait()
|
||||||
elif self.tar2_process.wait() != 0:
|
elif self.tar2_process.wait() != 0:
|
||||||
self.collect_tar_output()
|
self.collect_tar_output()
|
||||||
raise QubesException(
|
raise qubes.exc.QubesException(
|
||||||
"unable to extract files for {0}.{1} Tar command "
|
"unable to extract files for {0}.{1} Tar command "
|
||||||
"output: %s".
|
"output: %s".
|
||||||
format(self.tar2_current_file,
|
format(self.tar2_current_file,
|
||||||
@ -1241,7 +1266,7 @@ class ExtractWorker3(ExtractWorker2):
|
|||||||
self.tar2_process.wait()
|
self.tar2_process.wait()
|
||||||
elif self.tar2_process.wait() != 0:
|
elif self.tar2_process.wait() != 0:
|
||||||
self.collect_tar_output()
|
self.collect_tar_output()
|
||||||
raise QubesException(
|
raise qubes.exc.QubesException(
|
||||||
"unable to extract files for {0}.{1} Tar command "
|
"unable to extract files for {0}.{1} Tar command "
|
||||||
"output: %s".
|
"output: %s".
|
||||||
format(self.tar2_current_file,
|
format(self.tar2_current_file,
|
||||||
@ -1274,7 +1299,7 @@ def parse_backup_header(filename):
|
|||||||
with open(filename, 'r') as f:
|
with open(filename, 'r') as f:
|
||||||
for line in f.readlines():
|
for line in f.readlines():
|
||||||
if line.count('=') != 1:
|
if line.count('=') != 1:
|
||||||
raise QubesException("Invalid backup header (line %s)" % line)
|
raise qubes.exc.QubesException("Invalid backup header (line %s)" % line)
|
||||||
(key, value) = line.strip().split('=')
|
(key, value) = line.strip().split('=')
|
||||||
if not any([key == getattr(BackupHeader, attr) for attr in dir(
|
if not any([key == getattr(BackupHeader, attr) for attr in dir(
|
||||||
BackupHeader)]):
|
BackupHeader)]):
|
||||||
@ -1394,7 +1419,7 @@ def restore_vm_dirs(backup_source, restore_tmpdir, passphrase, vms_dirs, vms,
|
|||||||
else:
|
else:
|
||||||
command.wait()
|
command.wait()
|
||||||
proc_error_msg = command.stderr.read(MAX_STDERR_BYTES)
|
proc_error_msg = command.stderr.read(MAX_STDERR_BYTES)
|
||||||
raise QubesException("Premature end of archive while receiving "
|
raise qubes.exc.QubesException("Premature end of archive while receiving "
|
||||||
"backup header. Process output:\n" +
|
"backup header. Process output:\n" +
|
||||||
proc_error_msg)
|
proc_error_msg)
|
||||||
filename = os.path.join(restore_tmpdir, filename)
|
filename = os.path.join(restore_tmpdir, filename)
|
||||||
@ -1406,11 +1431,11 @@ def restore_vm_dirs(backup_source, restore_tmpdir, passphrase, vms_dirs, vms,
|
|||||||
file_ok = True
|
file_ok = True
|
||||||
hmac_algorithm = hmac_algo
|
hmac_algorithm = hmac_algo
|
||||||
break
|
break
|
||||||
except QubesException:
|
except qubes.exc.QubesException:
|
||||||
# Ignore exception here, try the next algo
|
# Ignore exception here, try the next algo
|
||||||
pass
|
pass
|
||||||
if not file_ok:
|
if not file_ok:
|
||||||
raise QubesException("Corrupted backup header (hmac verification "
|
raise qubes.exc.QubesException("Corrupted backup header (hmac verification "
|
||||||
"failed). Is the password correct?")
|
"failed). Is the password correct?")
|
||||||
if os.path.basename(filename) == HEADER_FILENAME:
|
if os.path.basename(filename) == HEADER_FILENAME:
|
||||||
header_data = parse_backup_header(filename)
|
header_data = parse_backup_header(filename)
|
||||||
@ -1524,18 +1549,18 @@ def restore_vm_dirs(backup_source, restore_tmpdir, passphrase, vms_dirs, vms,
|
|||||||
tmpdir=restore_tmpdir)
|
tmpdir=restore_tmpdir)
|
||||||
|
|
||||||
if command.wait() != 0 and not expect_tar_error:
|
if command.wait() != 0 and not expect_tar_error:
|
||||||
raise QubesException(
|
raise qubes.exc.QubesException(
|
||||||
"unable to read the qubes backup file {0} ({1}). "
|
"unable to read the qubes backup file {0} ({1}). "
|
||||||
"Is it really a backup?".format(backup_source, command.wait()))
|
"Is it really a backup?".format(backup_source, command.wait()))
|
||||||
if vmproc:
|
if vmproc:
|
||||||
if vmproc.wait() != 0:
|
if vmproc.wait() != 0:
|
||||||
raise QubesException(
|
raise qubes.exc.QubesException(
|
||||||
"unable to read the qubes backup {0} "
|
"unable to read the qubes backup {0} "
|
||||||
"because of a VM error: {1}".format(
|
"because of a VM error: {1}".format(
|
||||||
backup_source, vmproc.stderr.read(MAX_STDERR_BYTES)))
|
backup_source, vmproc.stderr.read(MAX_STDERR_BYTES)))
|
||||||
|
|
||||||
if filename and filename != "EOF":
|
if filename and filename != "EOF":
|
||||||
raise QubesException(
|
raise qubes.exc.QubesException(
|
||||||
"Premature end of archive, the last file was %s" % filename)
|
"Premature end of archive, the last file was %s" % filename)
|
||||||
except:
|
except:
|
||||||
to_extract.put("ERROR")
|
to_extract.put("ERROR")
|
||||||
@ -1551,7 +1576,7 @@ def restore_vm_dirs(backup_source, restore_tmpdir, passphrase, vms_dirs, vms,
|
|||||||
print_callback("Extraction process finished with code:" +
|
print_callback("Extraction process finished with code:" +
|
||||||
str(extract_proc.exitcode))
|
str(extract_proc.exitcode))
|
||||||
if extract_proc.exitcode != 0:
|
if extract_proc.exitcode != 0:
|
||||||
raise QubesException(
|
raise qubes.exc.QubesException(
|
||||||
"unable to extract the qubes backup. "
|
"unable to extract the qubes backup. "
|
||||||
"Check extracting process errors.")
|
"Check extracting process errors.")
|
||||||
|
|
||||||
@ -1584,7 +1609,7 @@ def load_hmac(hmac):
|
|||||||
if len(hmac) > 1:
|
if len(hmac) > 1:
|
||||||
hmac = hmac[1].strip()
|
hmac = hmac[1].strip()
|
||||||
else:
|
else:
|
||||||
raise QubesException("ERROR: invalid hmac file content")
|
raise qubes.exc.QubesException("ERROR: invalid hmac file content")
|
||||||
|
|
||||||
return hmac
|
return hmac
|
||||||
|
|
||||||
@ -1642,6 +1667,7 @@ def backup_restore_header(source, passphrase,
|
|||||||
return (restore_tmpdir, os.path.join(restore_tmpdir, "qubes.xml"),
|
return (restore_tmpdir, os.path.join(restore_tmpdir, "qubes.xml"),
|
||||||
header_data)
|
header_data)
|
||||||
|
|
||||||
|
|
||||||
def generate_new_name_for_conflicting_vm(orig_name, host_collection,
|
def generate_new_name_for_conflicting_vm(orig_name, host_collection,
|
||||||
restore_info):
|
restore_info):
|
||||||
number = 1
|
number = 1
|
||||||
@ -1651,7 +1677,7 @@ def generate_new_name_for_conflicting_vm(orig_name, host_collection,
|
|||||||
while (new_name in restore_info.keys() or
|
while (new_name in restore_info.keys() or
|
||||||
new_name in map(lambda x: x.get('rename_to', None),
|
new_name in map(lambda x: x.get('rename_to', None),
|
||||||
restore_info.values()) or
|
restore_info.values()) or
|
||||||
host_collection.get_vm_by_name(new_name)):
|
new_name in host_collection.domains):
|
||||||
new_name = str('{}{}'.format(orig_name, number))
|
new_name = str('{}{}'.format(orig_name, number))
|
||||||
number += 1
|
number += 1
|
||||||
if number == 100:
|
if number == 100:
|
||||||
@ -1660,6 +1686,7 @@ def generate_new_name_for_conflicting_vm(orig_name, host_collection,
|
|||||||
return new_name
|
return new_name
|
||||||
|
|
||||||
def restore_info_verify(restore_info, host_collection):
|
def restore_info_verify(restore_info, host_collection):
|
||||||
|
assert isinstance(host_collection, qubes.Qubes)
|
||||||
options = restore_info['$OPTIONS$']
|
options = restore_info['$OPTIONS$']
|
||||||
for vm in restore_info.keys():
|
for vm in restore_info.keys():
|
||||||
if vm in ['$OPTIONS$', 'dom0']:
|
if vm in ['$OPTIONS$', 'dom0']:
|
||||||
@ -1674,7 +1701,7 @@ def restore_info_verify(restore_info, host_collection):
|
|||||||
|
|
||||||
vm_info.pop('already-exists', None)
|
vm_info.pop('already-exists', None)
|
||||||
if not options['verify-only'] and \
|
if not options['verify-only'] and \
|
||||||
host_collection.get_vm_by_name(vm) is not None:
|
vm in host_collection.domains:
|
||||||
if options['rename-conflicting']:
|
if options['rename-conflicting']:
|
||||||
new_name = generate_new_name_for_conflicting_vm(
|
new_name = generate_new_name_for_conflicting_vm(
|
||||||
vm, host_collection, restore_info
|
vm, host_collection, restore_info
|
||||||
@ -1690,7 +1717,7 @@ def restore_info_verify(restore_info, host_collection):
|
|||||||
vm_info.pop('missing-template', None)
|
vm_info.pop('missing-template', None)
|
||||||
if vm_info['template']:
|
if vm_info['template']:
|
||||||
template_name = vm_info['template']
|
template_name = vm_info['template']
|
||||||
host_template = host_collection.get_vm_by_name(template_name)
|
host_template = host_collection.domains.get(template_name, None)
|
||||||
if not host_template or not host_template.is_template():
|
if not host_template or not host_template.is_template():
|
||||||
# Maybe the (custom) template is in the backup?
|
# Maybe the (custom) template is in the backup?
|
||||||
if not (template_name in restore_info.keys() and
|
if not (template_name in restore_info.keys() and
|
||||||
@ -1699,7 +1726,7 @@ def restore_info_verify(restore_info, host_collection):
|
|||||||
if 'orig-template' not in vm_info.keys():
|
if 'orig-template' not in vm_info.keys():
|
||||||
vm_info['orig-template'] = template_name
|
vm_info['orig-template'] = template_name
|
||||||
vm_info['template'] = host_collection \
|
vm_info['template'] = host_collection \
|
||||||
.get_default_template().name
|
.default_template.name
|
||||||
else:
|
else:
|
||||||
vm_info['missing-template'] = True
|
vm_info['missing-template'] = True
|
||||||
|
|
||||||
@ -1708,7 +1735,7 @@ def restore_info_verify(restore_info, host_collection):
|
|||||||
if vm_info['netvm']:
|
if vm_info['netvm']:
|
||||||
netvm_name = vm_info['netvm']
|
netvm_name = vm_info['netvm']
|
||||||
|
|
||||||
netvm_on_host = host_collection.get_vm_by_name(netvm_name)
|
netvm_on_host = host_collection.domains.get(netvm_name, None)
|
||||||
|
|
||||||
# No netvm on the host?
|
# No netvm on the host?
|
||||||
if not ((netvm_on_host is not None) and netvm_on_host.is_netvm()):
|
if not ((netvm_on_host is not None) and netvm_on_host.is_netvm()):
|
||||||
@ -1718,7 +1745,7 @@ def restore_info_verify(restore_info, host_collection):
|
|||||||
restore_info[netvm_name]['vm'].is_netvm()):
|
restore_info[netvm_name]['vm'].is_netvm()):
|
||||||
if options['use-default-netvm']:
|
if options['use-default-netvm']:
|
||||||
vm_info['netvm'] = host_collection \
|
vm_info['netvm'] = host_collection \
|
||||||
.get_default_netvm().name
|
.default_netvm.name
|
||||||
vm_info['vm'].uses_default_netvm = True
|
vm_info['vm'].uses_default_netvm = True
|
||||||
elif options['use-none-netvm']:
|
elif options['use-none-netvm']:
|
||||||
vm_info['netvm'] = None
|
vm_info['netvm'] = None
|
||||||
@ -1775,7 +1802,7 @@ def backup_restore_prepare(backup_location, passphrase, options=None,
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
backup_vm_dir_path = check_vm.dir_path.replace(
|
backup_vm_dir_path = check_vm.dir_path.replace(
|
||||||
system_path["qubes_base_dir"], backup_dir)
|
qubes.config.system_path["qubes_base_dir"], backup_dir)
|
||||||
|
|
||||||
if os.path.exists(backup_vm_dir_path):
|
if os.path.exists(backup_vm_dir_path):
|
||||||
return True
|
return True
|
||||||
@ -1812,11 +1839,11 @@ def backup_restore_prepare(backup_location, passphrase, options=None,
|
|||||||
is_vm_included_in_backup = is_vm_included_in_backup_v2
|
is_vm_included_in_backup = is_vm_included_in_backup_v2
|
||||||
if not appvm:
|
if not appvm:
|
||||||
if not os.path.isfile(backup_location):
|
if not os.path.isfile(backup_location):
|
||||||
raise QubesException("Invalid backup location (not a file or "
|
raise qubes.exc.QubesException("Invalid backup location (not a file or "
|
||||||
"directory with qubes.xml)"
|
"directory with qubes.xml)"
|
||||||
": %s" % unicode(backup_location))
|
": %s" % unicode(backup_location))
|
||||||
else:
|
else:
|
||||||
raise QubesException(
|
raise qubes.exc.QubesException(
|
||||||
"Unknown backup format version: %s" % str(format_version))
|
"Unknown backup format version: %s" % str(format_version))
|
||||||
|
|
||||||
(restore_tmpdir, qubes_xml, header_data) = backup_restore_header(
|
(restore_tmpdir, qubes_xml, header_data) = backup_restore_header(
|
||||||
@ -1847,17 +1874,12 @@ def backup_restore_prepare(backup_location, passphrase, options=None,
|
|||||||
|
|
||||||
if BACKUP_DEBUG:
|
if BACKUP_DEBUG:
|
||||||
print "Loading file", qubes_xml
|
print "Loading file", qubes_xml
|
||||||
backup_collection = QubesVmCollection(store_filename=qubes_xml)
|
backup_collection = qubes.Qubes(qubes_xml)
|
||||||
backup_collection.lock_db_for_reading()
|
|
||||||
backup_collection.load()
|
|
||||||
|
|
||||||
if host_collection is None:
|
if host_collection is None:
|
||||||
host_collection = QubesVmCollection()
|
host_collection = qubes.Qubes()
|
||||||
host_collection.lock_db_for_reading()
|
|
||||||
host_collection.load()
|
|
||||||
host_collection.unlock_db()
|
|
||||||
|
|
||||||
backup_vms_list = [vm for vm in backup_collection.values()]
|
backup_vms_list = [vm for vm in backup_collection.domains]
|
||||||
vms_to_restore = {}
|
vms_to_restore = {}
|
||||||
|
|
||||||
# ... and the actual data
|
# ... and the actual data
|
||||||
@ -1907,8 +1929,9 @@ def backup_restore_prepare(backup_location, passphrase, options=None,
|
|||||||
|
|
||||||
# ...and dom0 home
|
# ...and dom0 home
|
||||||
if options['dom0-home'] and \
|
if options['dom0-home'] and \
|
||||||
is_vm_included_in_backup(backup_location, backup_collection[0]):
|
is_vm_included_in_backup(backup_location,
|
||||||
vm = backup_collection[0]
|
backup_collection.domains[0]):
|
||||||
|
vm = backup_collection.domains[0]
|
||||||
vms_to_restore['dom0'] = {}
|
vms_to_restore['dom0'] = {}
|
||||||
if format_version == 1:
|
if format_version == 1:
|
||||||
vms_to_restore['dom0']['subdir'] = \
|
vms_to_restore['dom0']['subdir'] = \
|
||||||
@ -2054,13 +2077,13 @@ def backup_restore_do(restore_info,
|
|||||||
# Private functions begin
|
# Private functions begin
|
||||||
def restore_vm_dir_v1(backup_dir, src_dir, dst_dir):
|
def restore_vm_dir_v1(backup_dir, src_dir, dst_dir):
|
||||||
|
|
||||||
backup_src_dir = src_dir.replace(system_path["qubes_base_dir"],
|
backup_src_dir = src_dir.replace(
|
||||||
backup_dir)
|
qubes.config.system_path["qubes_base_dir"], backup_dir)
|
||||||
|
|
||||||
# We prefer to use Linux's cp, because it nicely handles sparse files
|
# We prefer to use Linux's cp, because it nicely handles sparse files
|
||||||
cp_retcode = subprocess.call(["cp", "-rp", "--reflink=auto", backup_src_dir, dst_dir])
|
cp_retcode = subprocess.call(["cp", "-rp", "--reflink=auto", backup_src_dir, dst_dir])
|
||||||
if cp_retcode != 0:
|
if cp_retcode != 0:
|
||||||
raise QubesException(
|
raise qubes.exc.QubesException(
|
||||||
"*** Error while copying file {0} to {1}".format(backup_src_dir,
|
"*** Error while copying file {0} to {1}".format(backup_src_dir,
|
||||||
dst_dir))
|
dst_dir))
|
||||||
|
|
||||||
@ -2084,9 +2107,8 @@ def backup_restore_do(restore_info,
|
|||||||
|
|
||||||
lock_obtained = False
|
lock_obtained = False
|
||||||
if host_collection is None:
|
if host_collection is None:
|
||||||
host_collection = QubesVmCollection()
|
host_collection = qubes.Qubes()
|
||||||
host_collection.lock_db_for_writing()
|
# FIXME
|
||||||
host_collection.load()
|
|
||||||
lock_obtained = True
|
lock_obtained = True
|
||||||
|
|
||||||
# Perform VM restoration in backup order
|
# Perform VM restoration in backup order
|
||||||
@ -2129,7 +2151,7 @@ def backup_restore_do(restore_info,
|
|||||||
compressed=compressed,
|
compressed=compressed,
|
||||||
compression_filter=compression_filter,
|
compression_filter=compression_filter,
|
||||||
appvm=appvm)
|
appvm=appvm)
|
||||||
except QubesException:
|
except qubes.exc.QubesException:
|
||||||
if verify_only:
|
if verify_only:
|
||||||
raise
|
raise
|
||||||
else:
|
else:
|
||||||
@ -2148,9 +2170,8 @@ def backup_restore_do(restore_info,
|
|||||||
shutil.rmtree(restore_tmpdir)
|
shutil.rmtree(restore_tmpdir)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Add VM in right order
|
# First load templates, then other VMs
|
||||||
for (vm_class_name, vm_class) in sorted(QubesVmClasses.items(),
|
for do_templates in (True, False):
|
||||||
key=lambda _x: _x[1].load_order):
|
|
||||||
if running_backup_operation.canceled:
|
if running_backup_operation.canceled:
|
||||||
break
|
break
|
||||||
for vm in vms.values():
|
for vm in vms.values():
|
||||||
@ -2158,11 +2179,10 @@ def backup_restore_do(restore_info,
|
|||||||
# only break the loop to save qubes.xml with already restored
|
# only break the loop to save qubes.xml with already restored
|
||||||
# VMs
|
# VMs
|
||||||
break
|
break
|
||||||
if not vm.__class__ == vm_class:
|
if vm.is_template != do_templates:
|
||||||
continue
|
continue
|
||||||
if callable(print_callback):
|
if callable(print_callback):
|
||||||
print_callback("-> Restoring {type} {0}...".
|
print_callback("-> Restoring {0}...".format(vm.name))
|
||||||
format(vm.name, type=vm_class_name))
|
|
||||||
retcode = subprocess.call(
|
retcode = subprocess.call(
|
||||||
["mkdir", "-p", os.path.dirname(vm.dir_path)])
|
["mkdir", "-p", os.path.dirname(vm.dir_path)])
|
||||||
if retcode != 0:
|
if retcode != 0:
|
||||||
@ -2171,10 +2191,12 @@ def backup_restore_do(restore_info,
|
|||||||
error_callback("Skipping...")
|
error_callback("Skipping...")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
template = None
|
kwargs = {}
|
||||||
if vm.template is not None:
|
if hasattr(vm, 'template'):
|
||||||
template_name = restore_info[vm.name]['template']
|
if vm.template is not None:
|
||||||
template = host_collection.get_vm_by_name(template_name)
|
kwargs['template'] = restore_info[vm.name]['template']
|
||||||
|
else:
|
||||||
|
kwargs['template'] = None
|
||||||
|
|
||||||
new_vm = None
|
new_vm = None
|
||||||
vm_name = vm.name
|
vm_name = vm.name
|
||||||
@ -2182,9 +2204,13 @@ def backup_restore_do(restore_info,
|
|||||||
vm_name = restore_info[vm.name]['rename-to']
|
vm_name = restore_info[vm.name]['rename-to']
|
||||||
|
|
||||||
try:
|
try:
|
||||||
new_vm = host_collection.add_new_vm(vm_class_name, name=vm_name,
|
# first only minimal set, later clone_properties will be called
|
||||||
template=template,
|
new_vm = host_collection.add_new_vm(
|
||||||
installed_by_rpm=False)
|
vm.__class__,
|
||||||
|
name=vm_name,
|
||||||
|
label=vm.label,
|
||||||
|
installed_by_rpm=False,
|
||||||
|
**kwargs)
|
||||||
if os.path.exists(new_vm.dir_path):
|
if os.path.exists(new_vm.dir_path):
|
||||||
move_to_path = tempfile.mkdtemp('', os.path.basename(
|
move_to_path = tempfile.mkdtemp('', os.path.basename(
|
||||||
new_vm.dir_path), os.path.dirname(new_vm.dir_path))
|
new_vm.dir_path), os.path.dirname(new_vm.dir_path))
|
||||||
@ -2214,20 +2240,19 @@ def backup_restore_do(restore_info,
|
|||||||
error_callback("ERROR: {0}".format(err))
|
error_callback("ERROR: {0}".format(err))
|
||||||
error_callback("*** Skipping VM: {0}".format(vm.name))
|
error_callback("*** Skipping VM: {0}".format(vm.name))
|
||||||
if new_vm:
|
if new_vm:
|
||||||
host_collection.pop(new_vm.qid)
|
del host_collection.domains[new_vm.qid]
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# FIXME: cannot check for 'kernel' property, because it is always
|
if hasattr(vm, 'kernel'):
|
||||||
# defined - accessing it touches non-existent '_kernel'
|
|
||||||
if not isinstance(vm, QubesVmClasses['QubesHVm']):
|
|
||||||
# TODO: add a setting for this?
|
# TODO: add a setting for this?
|
||||||
if vm.kernel and vm.kernel not in \
|
if not vm.is_property_default('kernel') and \
|
||||||
os.listdir(system_path['qubes_kernels_base_dir']):
|
vm.kernel not in \
|
||||||
|
os.listdir(qubes.config.system_path[
|
||||||
|
'qubes_kernels_base_dir']):
|
||||||
if callable(print_callback):
|
if callable(print_callback):
|
||||||
print_callback("WARNING: Kernel %s not installed, "
|
print_callback("WARNING: Kernel %s not installed, "
|
||||||
"using default one" % vm.kernel)
|
"using default one" % vm.kernel)
|
||||||
vm.uses_default_kernel = True
|
vm.kernel = qubes.property.DEFAULT
|
||||||
vm.kernel = host_collection.get_default_kernel()
|
|
||||||
try:
|
try:
|
||||||
new_vm.clone_attrs(vm)
|
new_vm.clone_attrs(vm)
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
@ -2235,7 +2260,7 @@ def backup_restore_do(restore_info,
|
|||||||
error_callback("*** Some VM property will not be restored")
|
error_callback("*** Some VM property will not be restored")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
new_vm.appmenus_create(verbose=callable(print_callback))
|
new_vm.appmenus_create()
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
error_callback("ERROR during appmenu restore: {0}".format(err))
|
error_callback("ERROR during appmenu restore: {0}".format(err))
|
||||||
error_callback(
|
error_callback(
|
||||||
@ -2246,22 +2271,19 @@ def backup_restore_do(restore_info,
|
|||||||
vm_name = vm.name
|
vm_name = vm.name
|
||||||
if 'rename-to' in restore_info[vm.name]:
|
if 'rename-to' in restore_info[vm.name]:
|
||||||
vm_name = restore_info[vm.name]['rename-to']
|
vm_name = restore_info[vm.name]['rename-to']
|
||||||
host_vm = host_collection.get_vm_by_name(vm_name)
|
try:
|
||||||
|
host_vm = host_collection.domains[vm_name]
|
||||||
if host_vm is None:
|
except KeyError:
|
||||||
# Failed/skipped VM
|
# Failed/skipped VM
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if not vm.uses_default_netvm:
|
if not vm.is_property_default('netvm'):
|
||||||
if restore_info[vm.name]['netvm'] is not None:
|
if restore_info[vm.name]['netvm'] is not None:
|
||||||
host_vm.netvm = host_collection.get_vm_by_name(
|
host_vm.netvm = restore_info[vm.name]['netvm']
|
||||||
restore_info[vm.name]['netvm'])
|
|
||||||
else:
|
else:
|
||||||
host_vm.netvm = None
|
host_vm.netvm = None
|
||||||
|
|
||||||
host_collection.save()
|
host_collection.save()
|
||||||
if lock_obtained:
|
|
||||||
host_collection.unlock_db()
|
|
||||||
|
|
||||||
if running_backup_operation.canceled:
|
if running_backup_operation.canceled:
|
||||||
if format_version >= 2:
|
if format_version >= 2:
|
@ -40,6 +40,8 @@ import time
|
|||||||
|
|
||||||
import qubes.config
|
import qubes.config
|
||||||
import qubes.events
|
import qubes.events
|
||||||
|
import qubes.backup
|
||||||
|
import qubes.exc
|
||||||
|
|
||||||
XMLPATH = '/var/lib/qubes/qubes-test.xml'
|
XMLPATH = '/var/lib/qubes/qubes-test.xml'
|
||||||
CLASS_XMLPATH = '/var/lib/qubes/qubes-class-test.xml'
|
CLASS_XMLPATH = '/var/lib/qubes/qubes-class-test.xml'
|
||||||
@ -482,7 +484,7 @@ class SystemTestsMixin(object):
|
|||||||
except (AttributeError, libvirt.libvirtError):
|
except (AttributeError, libvirt.libvirtError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
del app.domains[vm]
|
del app.domains[vm.qid]
|
||||||
del vm
|
del vm
|
||||||
|
|
||||||
app.save()
|
app.save()
|
||||||
@ -686,8 +688,8 @@ class BackupTestsMixin(SystemTestsMixin):
|
|||||||
if self.verbose:
|
if self.verbose:
|
||||||
print >>sys.stderr, "-> Creating %s" % vmname
|
print >>sys.stderr, "-> Creating %s" % vmname
|
||||||
testnet = self.app.add_new_vm(qubes.vm.appvm.AppVM,
|
testnet = self.app.add_new_vm(qubes.vm.appvm.AppVM,
|
||||||
name=vmname, template=template, provides_network=True)
|
name=vmname, template=template, provides_network=True, label='red')
|
||||||
testnet.create_on_disk(verbose=self.verbose)
|
testnet.create_on_disk()
|
||||||
vms.append(testnet)
|
vms.append(testnet)
|
||||||
self.fill_image(testnet.private_img, 20*1024*1024)
|
self.fill_image(testnet.private_img, 20*1024*1024)
|
||||||
|
|
||||||
@ -695,10 +697,10 @@ class BackupTestsMixin(SystemTestsMixin):
|
|||||||
if self.verbose:
|
if self.verbose:
|
||||||
print >>sys.stderr, "-> Creating %s" % vmname
|
print >>sys.stderr, "-> Creating %s" % vmname
|
||||||
testvm1 = self.app.add_new_vm(qubes.vm.appvm.AppVM,
|
testvm1 = self.app.add_new_vm(qubes.vm.appvm.AppVM,
|
||||||
name=vmname, template=template)
|
name=vmname, template=template, label='red')
|
||||||
testvm1.uses_default_netvm = False
|
testvm1.uses_default_netvm = False
|
||||||
testvm1.netvm = testnet
|
testvm1.netvm = testnet
|
||||||
testvm1.create_on_disk(verbose=self.verbose)
|
testvm1.create_on_disk()
|
||||||
vms.append(testvm1)
|
vms.append(testvm1)
|
||||||
self.fill_image(testvm1.private_img, 100*1024*1024)
|
self.fill_image(testvm1.private_img, 100*1024*1024)
|
||||||
|
|
||||||
@ -706,8 +708,8 @@ class BackupTestsMixin(SystemTestsMixin):
|
|||||||
if self.verbose:
|
if self.verbose:
|
||||||
print >>sys.stderr, "-> Creating %s" % vmname
|
print >>sys.stderr, "-> Creating %s" % vmname
|
||||||
testvm2 = self.app.add_new_vm(qubes.vm.appvm.AppVM, name=vmname,
|
testvm2 = self.app.add_new_vm(qubes.vm.appvm.AppVM, name=vmname,
|
||||||
hvm=True)
|
hvm=True, label='red')
|
||||||
testvm2.create_on_disk(verbose=self.verbose)
|
testvm2.create_on_disk()
|
||||||
self.fill_image(testvm2.root_img, 1024*1024*1024, True)
|
self.fill_image(testvm2.root_img, 1024*1024*1024, True)
|
||||||
vms.append(testvm2)
|
vms.append(testvm2)
|
||||||
|
|
||||||
@ -717,33 +719,31 @@ class BackupTestsMixin(SystemTestsMixin):
|
|||||||
|
|
||||||
def make_backup(self, vms, prepare_kwargs=dict(), do_kwargs=dict(),
|
def make_backup(self, vms, prepare_kwargs=dict(), do_kwargs=dict(),
|
||||||
target=None, expect_failure=False):
|
target=None, expect_failure=False):
|
||||||
# XXX: bakup_prepare and backup_do don't support host_collection
|
|
||||||
# self.qc.unlock_db()
|
|
||||||
if target is None:
|
if target is None:
|
||||||
target = self.backupdir
|
target = self.backupdir
|
||||||
try:
|
try:
|
||||||
files_to_backup = \
|
files_to_backup = \
|
||||||
qubes.backup.backup_prepare(vms,
|
qubes.backup.backup_prepare(self.app, vms,
|
||||||
print_callback=self.print_callback,
|
print_callback=self.print_callback,
|
||||||
**prepare_kwargs)
|
**prepare_kwargs)
|
||||||
except qubes.qubes.QubesException as e:
|
except qubes.exc.QubesException as e:
|
||||||
if not expect_failure:
|
if not expect_failure:
|
||||||
self.fail("QubesException during backup_prepare: %s" % str(e))
|
self.fail("QubesException during backup_prepare: %s" % str(e))
|
||||||
else:
|
else:
|
||||||
raise
|
raise
|
||||||
|
|
||||||
try:
|
try:
|
||||||
qubes.backup.backup_do(target, files_to_backup, "qubes",
|
qubes.backup.backup_do(self.app, target, files_to_backup, "qubes",
|
||||||
progress_callback=self.print_progress,
|
progress_callback=self.print_progress,
|
||||||
**do_kwargs)
|
**do_kwargs)
|
||||||
except qubes.qubes.QubesException as e:
|
except qubes.exc.QubesException as e:
|
||||||
if not expect_failure:
|
if not expect_failure:
|
||||||
self.fail("QubesException during backup_do: %s" % str(e))
|
self.fail("QubesException during backup_do: %s" % str(e))
|
||||||
else:
|
else:
|
||||||
raise
|
raise
|
||||||
|
|
||||||
# FIXME why?
|
# FIXME why?
|
||||||
self.reload_db()
|
#self.reload_db()
|
||||||
|
|
||||||
def restore_backup(self, source=None, appvm=None, options=None,
|
def restore_backup(self, source=None, appvm=None, options=None,
|
||||||
expect_errors=None):
|
expect_errors=None):
|
||||||
@ -753,7 +753,7 @@ class BackupTestsMixin(SystemTestsMixin):
|
|||||||
else:
|
else:
|
||||||
backupfile = source
|
backupfile = source
|
||||||
|
|
||||||
with self.assertNotRaises(qubes.qubes.QubesException):
|
with self.assertNotRaises(qubes.exc.QubesException):
|
||||||
backup_info = qubes.backup.backup_restore_prepare(
|
backup_info = qubes.backup.backup_restore_prepare(
|
||||||
backupfile, "qubes",
|
backupfile, "qubes",
|
||||||
host_collection=self.app,
|
host_collection=self.app,
|
||||||
@ -764,7 +764,7 @@ class BackupTestsMixin(SystemTestsMixin):
|
|||||||
if self.verbose:
|
if self.verbose:
|
||||||
qubes.backup.backup_restore_print_summary(backup_info)
|
qubes.backup.backup_restore_print_summary(backup_info)
|
||||||
|
|
||||||
with self.assertNotRaises(qubes.qubes.QubesException):
|
with self.assertNotRaises(qubes.exc.QubesException):
|
||||||
qubes.backup.backup_restore_do(
|
qubes.backup.backup_restore_do(
|
||||||
backup_info,
|
backup_info,
|
||||||
host_collection=self.app,
|
host_collection=self.app,
|
||||||
@ -821,7 +821,7 @@ def load_tests(loader, tests, pattern): # pylint: disable=unused-argument
|
|||||||
'qubes.tests.int.dom0_update',
|
'qubes.tests.int.dom0_update',
|
||||||
'qubes.tests.int.network',
|
'qubes.tests.int.network',
|
||||||
# 'qubes.tests.vm_qrexec_gui',
|
# 'qubes.tests.vm_qrexec_gui',
|
||||||
# 'qubes.tests.backup',
|
'qubes.tests.int.backup',
|
||||||
# 'qubes.tests.backupcompatibility',
|
# 'qubes.tests.backupcompatibility',
|
||||||
# 'qubes.tests.regressions',
|
# 'qubes.tests.regressions',
|
||||||
|
|
||||||
|
@ -28,8 +28,11 @@ import os
|
|||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
import sys
|
import sys
|
||||||
from qubes.qubes import QubesException, QubesTemplateVm
|
import qubes
|
||||||
|
import qubes.exc
|
||||||
import qubes.tests
|
import qubes.tests
|
||||||
|
import qubes.vm.appvm
|
||||||
|
import qubes.vm.templatevm
|
||||||
|
|
||||||
class TC_00_Backup(qubes.tests.BackupTestsMixin, qubes.tests.QubesTestCase):
|
class TC_00_Backup(qubes.tests.BackupTestsMixin, qubes.tests.QubesTestCase):
|
||||||
def test_000_basic_backup(self):
|
def test_000_basic_backup(self):
|
||||||
@ -37,21 +40,24 @@ class TC_00_Backup(qubes.tests.BackupTestsMixin, qubes.tests.QubesTestCase):
|
|||||||
self.make_backup(vms)
|
self.make_backup(vms)
|
||||||
self.remove_vms(vms)
|
self.remove_vms(vms)
|
||||||
self.restore_backup()
|
self.restore_backup()
|
||||||
self.remove_vms(vms)
|
for vm in vms:
|
||||||
|
self.assertIn(vm.name, self.app.domains)
|
||||||
|
|
||||||
def test_001_compressed_backup(self):
|
def test_001_compressed_backup(self):
|
||||||
vms = self.create_backup_vms()
|
vms = self.create_backup_vms()
|
||||||
self.make_backup(vms, do_kwargs={'compressed': True})
|
self.make_backup(vms, do_kwargs={'compressed': True})
|
||||||
self.remove_vms(vms)
|
self.remove_vms(vms)
|
||||||
self.restore_backup()
|
self.restore_backup()
|
||||||
self.remove_vms(vms)
|
for vm in vms:
|
||||||
|
self.assertIn(vm.name, self.app.domains)
|
||||||
|
|
||||||
def test_002_encrypted_backup(self):
|
def test_002_encrypted_backup(self):
|
||||||
vms = self.create_backup_vms()
|
vms = self.create_backup_vms()
|
||||||
self.make_backup(vms, do_kwargs={'encrypted': True})
|
self.make_backup(vms, do_kwargs={'encrypted': True})
|
||||||
self.remove_vms(vms)
|
self.remove_vms(vms)
|
||||||
self.restore_backup()
|
self.restore_backup()
|
||||||
self.remove_vms(vms)
|
for vm in vms:
|
||||||
|
self.assertIn(vm.name, self.app.domains)
|
||||||
|
|
||||||
def test_003_compressed_encrypted_backup(self):
|
def test_003_compressed_encrypted_backup(self):
|
||||||
vms = self.create_backup_vms()
|
vms = self.create_backup_vms()
|
||||||
@ -61,7 +67,8 @@ class TC_00_Backup(qubes.tests.BackupTestsMixin, qubes.tests.QubesTestCase):
|
|||||||
'encrypted': True})
|
'encrypted': True})
|
||||||
self.remove_vms(vms)
|
self.remove_vms(vms)
|
||||||
self.restore_backup()
|
self.restore_backup()
|
||||||
self.remove_vms(vms)
|
for vm in vms:
|
||||||
|
self.assertIn(vm.name, self.app.domains)
|
||||||
|
|
||||||
def test_004_sparse_multipart(self):
|
def test_004_sparse_multipart(self):
|
||||||
vms = []
|
vms = []
|
||||||
@ -70,29 +77,32 @@ class TC_00_Backup(qubes.tests.BackupTestsMixin, qubes.tests.QubesTestCase):
|
|||||||
if self.verbose:
|
if self.verbose:
|
||||||
print >>sys.stderr, "-> Creating %s" % vmname
|
print >>sys.stderr, "-> Creating %s" % vmname
|
||||||
|
|
||||||
hvmtemplate = self.qc.add_new_vm("QubesTemplateHVm", name=vmname)
|
hvmtemplate = self.app.add_new_vm(
|
||||||
hvmtemplate.create_on_disk(verbose=self.verbose)
|
qubes.vm.templatevm.TemplateVM, name=vmname, hvm=True, label='red')
|
||||||
|
hvmtemplate.create_on_disk()
|
||||||
self.fill_image(os.path.join(hvmtemplate.dir_path, '00file'),
|
self.fill_image(os.path.join(hvmtemplate.dir_path, '00file'),
|
||||||
195*1024*1024-4096*3)
|
195*1024*1024-4096*3)
|
||||||
self.fill_image(hvmtemplate.private_img, 195*1024*1024-4096*3)
|
self.fill_image(hvmtemplate.private_img, 195*1024*1024-4096*3)
|
||||||
self.fill_image(hvmtemplate.root_img, 1024*1024*1024, sparse=True)
|
self.fill_image(hvmtemplate.root_img, 1024*1024*1024, sparse=True)
|
||||||
vms.append(hvmtemplate)
|
vms.append(hvmtemplate)
|
||||||
self.qc.save()
|
self.app.save()
|
||||||
|
|
||||||
self.make_backup(vms)
|
self.make_backup(vms)
|
||||||
self.remove_vms(vms)
|
self.remove_vms(vms)
|
||||||
self.restore_backup()
|
self.restore_backup()
|
||||||
self.remove_vms(vms)
|
for vm in vms:
|
||||||
|
self.assertIn(vm.name, self.app.domains)
|
||||||
|
|
||||||
def test_005_compressed_custom(self):
|
def test_005_compressed_custom(self):
|
||||||
vms = self.create_backup_vms()
|
vms = self.create_backup_vms()
|
||||||
self.make_backup(vms, do_kwargs={'compressed': "bzip2"})
|
self.make_backup(vms, do_kwargs={'compressed': "bzip2"})
|
||||||
self.remove_vms(vms)
|
self.remove_vms(vms)
|
||||||
self.restore_backup()
|
self.restore_backup()
|
||||||
self.remove_vms(vms)
|
for vm in vms:
|
||||||
|
self.assertIn(vm.name, self.app.domains)
|
||||||
|
|
||||||
def test_100_backup_dom0_no_restore(self):
|
def test_100_backup_dom0_no_restore(self):
|
||||||
self.make_backup([self.qc[0]])
|
self.make_backup([self.app.domains[0]])
|
||||||
# TODO: think of some safe way to test restore...
|
# TODO: think of some safe way to test restore...
|
||||||
|
|
||||||
def test_200_restore_over_existing_directory(self):
|
def test_200_restore_over_existing_directory(self):
|
||||||
@ -112,7 +122,6 @@ class TC_00_Backup(qubes.tests.BackupTestsMixin, qubes.tests.QubesTestCase):
|
|||||||
'*** Directory {} already exists! It has been moved'.format(
|
'*** Directory {} already exists! It has been moved'.format(
|
||||||
test_dir)
|
test_dir)
|
||||||
])
|
])
|
||||||
self.remove_vms(vms)
|
|
||||||
|
|
||||||
def test_210_auto_rename(self):
|
def test_210_auto_rename(self):
|
||||||
"""
|
"""
|
||||||
@ -125,22 +134,23 @@ class TC_00_Backup(qubes.tests.BackupTestsMixin, qubes.tests.QubesTestCase):
|
|||||||
'rename-conflicting': True
|
'rename-conflicting': True
|
||||||
})
|
})
|
||||||
for vm in vms:
|
for vm in vms:
|
||||||
self.assertIsNotNone(self.qc.get_vm_by_name(vm.name+'1'))
|
with self.assertNotRaises(qubes.exc.QubesVMNotFoundError):
|
||||||
restored_vm = self.qc.get_vm_by_name(vm.name+'1')
|
restored_vm = self.app.domains[vm.name + '1']
|
||||||
if vm.netvm and not vm.uses_default_netvm:
|
if vm.netvm and not vm.property_is_default('netvm'):
|
||||||
self.assertEqual(restored_vm.netvm.name, vm.netvm.name+'1')
|
self.assertEqual(restored_vm.netvm.name, vm.netvm.name + '1')
|
||||||
|
|
||||||
|
|
||||||
self.remove_vms(vms)
|
|
||||||
|
|
||||||
class TC_10_BackupVMMixin(qubes.tests.BackupTestsMixin):
|
class TC_10_BackupVMMixin(qubes.tests.BackupTestsMixin):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(TC_10_BackupVMMixin, self).setUp()
|
super(TC_10_BackupVMMixin, self).setUp()
|
||||||
self.backupvm = self.qc.add_new_vm(
|
self.backupvm = self.app.add_new_vm(
|
||||||
"QubesAppVm",
|
qubes.vm.appvm.AppVM,
|
||||||
|
label='red',
|
||||||
name=self.make_vm_name('backupvm'),
|
name=self.make_vm_name('backupvm'),
|
||||||
template=self.qc.get_vm_by_name(self.template)
|
template=self.template
|
||||||
)
|
)
|
||||||
self.backupvm.create_on_disk(verbose=self.verbose)
|
self.backupvm.create_on_disk()
|
||||||
|
|
||||||
def test_100_send_to_vm_file_with_spaces(self):
|
def test_100_send_to_vm_file_with_spaces(self):
|
||||||
vms = self.create_backup_vms()
|
vms = self.create_backup_vms()
|
||||||
@ -159,7 +169,6 @@ class TC_10_BackupVMMixin(qubes.tests.BackupTestsMixin):
|
|||||||
backup_path = backup_path.strip()
|
backup_path = backup_path.strip()
|
||||||
self.restore_backup(source=backup_path,
|
self.restore_backup(source=backup_path,
|
||||||
appvm=self.backupvm)
|
appvm=self.backupvm)
|
||||||
self.remove_vms(vms)
|
|
||||||
|
|
||||||
def test_110_send_to_vm_command(self):
|
def test_110_send_to_vm_command(self):
|
||||||
vms = self.create_backup_vms()
|
vms = self.create_backup_vms()
|
||||||
@ -173,7 +182,6 @@ class TC_10_BackupVMMixin(qubes.tests.BackupTestsMixin):
|
|||||||
self.remove_vms(vms)
|
self.remove_vms(vms)
|
||||||
self.restore_backup(source='dd if=/var/tmp/backup-test',
|
self.restore_backup(source='dd if=/var/tmp/backup-test',
|
||||||
appvm=self.backupvm)
|
appvm=self.backupvm)
|
||||||
self.remove_vms(vms)
|
|
||||||
|
|
||||||
def test_110_send_to_vm_no_space(self):
|
def test_110_send_to_vm_no_space(self):
|
||||||
"""
|
"""
|
||||||
@ -192,7 +200,7 @@ class TC_10_BackupVMMixin(qubes.tests.BackupTestsMixin):
|
|||||||
user="root", wait=True)
|
user="root", wait=True)
|
||||||
if retcode != 0:
|
if retcode != 0:
|
||||||
raise RuntimeError("Failed to prepare backup directory")
|
raise RuntimeError("Failed to prepare backup directory")
|
||||||
with self.assertRaises(QubesException):
|
with self.assertRaises(qubes.exc.QubesException):
|
||||||
self.make_backup(vms,
|
self.make_backup(vms,
|
||||||
do_kwargs={
|
do_kwargs={
|
||||||
'appvm': self.backupvm,
|
'appvm': self.backupvm,
|
||||||
@ -200,19 +208,13 @@ class TC_10_BackupVMMixin(qubes.tests.BackupTestsMixin):
|
|||||||
'encrypted': True},
|
'encrypted': True},
|
||||||
target='/home/user/backup',
|
target='/home/user/backup',
|
||||||
expect_failure=True)
|
expect_failure=True)
|
||||||
self.qc.lock_db_for_writing()
|
|
||||||
self.qc.load()
|
|
||||||
self.remove_vms(vms)
|
|
||||||
|
|
||||||
|
|
||||||
def load_tests(loader, tests, pattern):
|
def load_tests(loader, tests, pattern):
|
||||||
try:
|
try:
|
||||||
qc = qubes.qubes.QubesVmCollection()
|
app = qubes.Qubes()
|
||||||
qc.lock_db_for_reading()
|
templates = [vm.name for vm in app.domains if
|
||||||
qc.load()
|
isinstance(vm, qubes.vm.templatevm.TemplateVM)]
|
||||||
qc.unlock_db()
|
|
||||||
templates = [vm.name for vm in qc.values() if
|
|
||||||
isinstance(vm, QubesTemplateVm)]
|
|
||||||
except OSError:
|
except OSError:
|
||||||
templates = []
|
templates = []
|
||||||
for template in templates:
|
for template in templates:
|
@ -90,9 +90,12 @@ def format_doc(docstring):
|
|||||||
# maybe adapt https://code.activestate.com/recipes/578019
|
# maybe adapt https://code.activestate.com/recipes/578019
|
||||||
def parse_size(size):
|
def parse_size(size):
|
||||||
units = [
|
units = [
|
||||||
('K', 1024), ('KB', 1024),
|
('K', 1000), ('KB', 1000),
|
||||||
('M', 1024*1024), ('MB', 1024*1024),
|
('M', 1000 * 1000), ('MB', 1000 * 1000),
|
||||||
('G', 1024*1024*1024), ('GB', 1024*1024*1024),
|
('G', 1000 * 1000 * 1000), ('GB', 1000 * 1000 * 1000),
|
||||||
|
('Ki', 1024), ('KiB', 1024),
|
||||||
|
('Mi', 1024 * 1024), ('MiB', 1024 * 1024),
|
||||||
|
('Gi', 1024 * 1024 * 1024), ('GiB', 1024 * 1024 * 1024),
|
||||||
]
|
]
|
||||||
|
|
||||||
size = size.strip().upper()
|
size = size.strip().upper()
|
||||||
@ -102,10 +105,43 @@ def parse_size(size):
|
|||||||
for unit, multiplier in units:
|
for unit, multiplier in units:
|
||||||
if size.endswith(unit):
|
if size.endswith(unit):
|
||||||
size = size[:-len(unit)].strip()
|
size = size[:-len(unit)].strip()
|
||||||
return int(size)*multiplier
|
return int(size) * multiplier
|
||||||
|
|
||||||
raise qubes.exc.QubesException("Invalid size: {0}.".format(size))
|
raise qubes.exc.QubesException("Invalid size: {0}.".format(size))
|
||||||
|
|
||||||
|
def 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 precision"""
|
||||||
|
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 urandom(size):
|
def urandom(size):
|
||||||
rand = os.urandom(size)
|
rand = os.urandom(size)
|
||||||
if rand is None:
|
if rand is None:
|
||||||
|
@ -201,6 +201,7 @@ fi
|
|||||||
|
|
||||||
%dir %{python_sitelib}/qubes
|
%dir %{python_sitelib}/qubes
|
||||||
%{python_sitelib}/qubes/__init__.py*
|
%{python_sitelib}/qubes/__init__.py*
|
||||||
|
%{python_sitelib}/qubes/backup.py*
|
||||||
%{python_sitelib}/qubes/config.py*
|
%{python_sitelib}/qubes/config.py*
|
||||||
%{python_sitelib}/qubes/devices.py*
|
%{python_sitelib}/qubes/devices.py*
|
||||||
%{python_sitelib}/qubes/dochelpers.py*
|
%{python_sitelib}/qubes/dochelpers.py*
|
||||||
@ -272,6 +273,7 @@ fi
|
|||||||
|
|
||||||
%dir %{python_sitelib}/qubes/tests/int
|
%dir %{python_sitelib}/qubes/tests/int
|
||||||
%{python_sitelib}/qubes/tests/int/__init__.py*
|
%{python_sitelib}/qubes/tests/int/__init__.py*
|
||||||
|
%{python_sitelib}/qubes/tests/int/backup.py*
|
||||||
%{python_sitelib}/qubes/tests/int/basic.py*
|
%{python_sitelib}/qubes/tests/int/basic.py*
|
||||||
%{python_sitelib}/qubes/tests/int/dom0_update.py*
|
%{python_sitelib}/qubes/tests/int/dom0_update.py*
|
||||||
%{python_sitelib}/qubes/tests/int/network.py*
|
%{python_sitelib}/qubes/tests/int/network.py*
|
||||||
|
Loading…
Reference in New Issue
Block a user