backup: code style fixes, no functional change (part 1)
Indentation, break long lines, use is/is not None instead of ==/!=.
This commit is contained in:
parent
e2d6ff653a
commit
9ec0580840
319
core/backup.py
319
core/backup.py
@ -3,7 +3,8 @@
|
|||||||
#
|
#
|
||||||
# The Qubes OS Project, http://www.qubes-os.org
|
# The Qubes OS Project, http://www.qubes-os.org
|
||||||
#
|
#
|
||||||
# Copyright (C) 2013 Marek Marczykowski-Górecki <marmarek@invisiblethingslab.com>
|
# Copyright (C) 2013-2015 Marek Marczykowski-Górecki
|
||||||
|
# <marmarek@invisiblethingslab.com>
|
||||||
# Copyright (C) 2013 Olivier Médoc <o_medoc@yahoo.fr>
|
# Copyright (C) 2013 Olivier Médoc <o_medoc@yahoo.fr>
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or
|
# This program is free software; you can redistribute it and/or
|
||||||
@ -34,7 +35,8 @@ import re
|
|||||||
import shutil
|
import shutil
|
||||||
import tempfile
|
import tempfile
|
||||||
import time
|
import time
|
||||||
import grp,pwd
|
import grp
|
||||||
|
import pwd
|
||||||
import errno
|
import errno
|
||||||
import datetime
|
import datetime
|
||||||
from multiprocessing import Queue, Process
|
from multiprocessing import Queue, Process
|
||||||
@ -54,17 +56,20 @@ HEADER_QUBES_XML_MAX_SIZE = 1024 * 1024
|
|||||||
# global state for backup_cancel()
|
# global state for backup_cancel()
|
||||||
running_backup_operation = None
|
running_backup_operation = None
|
||||||
|
|
||||||
|
|
||||||
class BackupOperationInfo:
|
class BackupOperationInfo:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.canceled = False
|
self.canceled = False
|
||||||
self.processes_to_kill_on_cancel = []
|
self.processes_to_kill_on_cancel = []
|
||||||
self.tmpdir_to_remove = None
|
self.tmpdir_to_remove = None
|
||||||
|
|
||||||
|
|
||||||
class BackupCanceledError(QubesException):
|
class BackupCanceledError(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
|
||||||
|
|
||||||
|
|
||||||
class BackupHeader:
|
class BackupHeader:
|
||||||
version = 'version'
|
version = 'version'
|
||||||
encrypted = 'encrypted'
|
encrypted = 'encrypted'
|
||||||
@ -91,6 +96,7 @@ def file_to_backup (file_path, subdir = None):
|
|||||||
subdir += '/'
|
subdir += '/'
|
||||||
return [{"path": file_path, "size": sz, "subdir": subdir}]
|
return [{"path": file_path, "size": sz, "subdir": subdir}]
|
||||||
|
|
||||||
|
|
||||||
def backup_cancel():
|
def backup_cancel():
|
||||||
"""
|
"""
|
||||||
Cancel currently running backup/restore operation
|
Cancel currently running backup/restore operation
|
||||||
@ -108,9 +114,13 @@ def backup_cancel():
|
|||||||
pass
|
pass
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def backup_prepare(vms_list=None, exclude_list=None,
|
def backup_prepare(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; exclude_list is always applied"""
|
"""
|
||||||
|
If vms = None, include all (sensible) VMs;
|
||||||
|
exclude_list is always applied
|
||||||
|
"""
|
||||||
files_to_backup = file_to_backup(system_path["qubes_store_filename"])
|
files_to_backup = file_to_backup(system_path["qubes_store_filename"])
|
||||||
|
|
||||||
if exclude_list is None:
|
if exclude_list is None:
|
||||||
@ -123,13 +133,16 @@ def backup_prepare(vms_list = None, exclude_list = None,
|
|||||||
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 qvm_collection.values()]
|
||||||
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 vm.is_appvm() and not vm.internal]
|
appvms_to_backup = [vm for vm in selected_vms if
|
||||||
netvms_to_backup = [vm for vm in selected_vms if vm.is_netvm() and not vm.qid == 0]
|
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 = [qvm_collection[0]]
|
||||||
|
|
||||||
vms_list = appvms_to_backup + netvms_to_backup + template_vms_worth_backingup + dom0
|
vms_list = appvms_to_backup + netvms_to_backup + \
|
||||||
|
template_vms_worth_backingup + dom0
|
||||||
|
|
||||||
vms_for_backup = vms_list
|
vms_for_backup = vms_list
|
||||||
# Apply exclude list
|
# Apply exclude list
|
||||||
@ -185,17 +198,20 @@ def backup_prepare(vms_list = None, exclude_list = None,
|
|||||||
if vm.updateable:
|
if vm.updateable:
|
||||||
if os.path.exists(vm.dir_path + "/apps.templates"):
|
if os.path.exists(vm.dir_path + "/apps.templates"):
|
||||||
# template
|
# template
|
||||||
files_to_backup += file_to_backup(vm.dir_path + "/apps.templates", subdir)
|
files_to_backup += file_to_backup(
|
||||||
|
vm.dir_path + "/apps.templates", subdir)
|
||||||
else:
|
else:
|
||||||
# standaloneVM
|
# standaloneVM
|
||||||
files_to_backup += file_to_backup(vm.dir_path + "/apps", subdir)
|
files_to_backup += file_to_backup(vm.dir_path + "/apps", subdir)
|
||||||
|
|
||||||
if os.path.exists(vm.dir_path + "/kernels"):
|
if os.path.exists(vm.dir_path + "/kernels"):
|
||||||
files_to_backup += file_to_backup(vm.dir_path + "/kernels", subdir)
|
files_to_backup += file_to_backup(vm.dir_path + "/kernels",
|
||||||
|
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 vm_files and \
|
||||||
os.path.exists(os.path.join(vm.dir_path, vm_files['appmenus_whitelist'])):
|
os.path.exists(os.path.join(vm.dir_path,
|
||||||
|
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, vm_files['appmenus_whitelist']),
|
||||||
subdir)
|
subdir)
|
||||||
@ -222,7 +238,8 @@ def backup_prepare(vms_list = None, exclude_list = None,
|
|||||||
s += fmt.format(size_to_human(vm_size))
|
s += fmt.format(size_to_human(vm_size))
|
||||||
|
|
||||||
if vm.is_running():
|
if vm.is_running():
|
||||||
s += " <-- The VM is running, please shut it down before proceeding with the backup!"
|
s += " <-- The VM is running, please shut it down before proceeding " \
|
||||||
|
"with the backup!"
|
||||||
there_are_running_vms = True
|
there_are_running_vms = True
|
||||||
|
|
||||||
print_callback(s)
|
print_callback(s)
|
||||||
@ -241,8 +258,7 @@ def backup_prepare(vms_list = None, exclude_list = None,
|
|||||||
template_subdir = os.path.relpath(
|
template_subdir = os.path.relpath(
|
||||||
vm.dir_path,
|
vm.dir_path,
|
||||||
system_path["qubes_base_dir"]) + '/'
|
system_path["qubes_base_dir"]) + '/'
|
||||||
template_to_backup = [ {
|
template_to_backup = [{"path": vm.dir_path + '/.',
|
||||||
"path": vm.dir_path + '/.',
|
|
||||||
"size": vm_sz,
|
"size": vm_sz,
|
||||||
"subdir": template_subdir}]
|
"subdir": template_subdir}]
|
||||||
files_to_backup += template_to_backup
|
files_to_backup += template_to_backup
|
||||||
@ -258,7 +274,8 @@ def backup_prepare(vms_list = None, exclude_list = None,
|
|||||||
s += fmt.format(size_to_human(vm_sz))
|
s += fmt.format(size_to_human(vm_sz))
|
||||||
|
|
||||||
if vm.is_running():
|
if vm.is_running():
|
||||||
s += " <-- The VM is running, please shut it down before proceeding with the backup!"
|
s += " <-- The VM is running, please shut it down before proceeding " \
|
||||||
|
"with the backup!"
|
||||||
there_are_running_vms = True
|
there_are_running_vms = True
|
||||||
|
|
||||||
print_callback(s)
|
print_callback(s)
|
||||||
@ -277,7 +294,8 @@ 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, system_path["qubes_base_dir"])
|
vm.backup_path = os.path.relpath(vm.dir_path,
|
||||||
|
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:
|
||||||
@ -289,7 +307,8 @@ def backup_prepare(vms_list = None, exclude_list = None,
|
|||||||
subprocess.check_call(['sudo', 'chown', '-R', local_user, home_dir])
|
subprocess.check_call(['sudo', 'chown', '-R', local_user, home_dir])
|
||||||
|
|
||||||
home_sz = get_disk_usage(home_dir)
|
home_sz = get_disk_usage(home_dir)
|
||||||
home_to_backup = [ { "path" : home_dir, "size": home_sz, "subdir": 'dom0-home/'} ]
|
home_to_backup = [
|
||||||
|
{"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 = qvm_collection[0]
|
||||||
@ -314,8 +333,8 @@ def backup_prepare(vms_list = None, exclude_list = None,
|
|||||||
qvm_collection.unlock_db()
|
qvm_collection.unlock_db()
|
||||||
|
|
||||||
total_backup_sz = 0
|
total_backup_sz = 0
|
||||||
for file in files_to_backup:
|
for f in files_to_backup:
|
||||||
total_backup_sz += file["size"]
|
total_backup_sz += f["size"]
|
||||||
|
|
||||||
s = ""
|
s = ""
|
||||||
for f in fields_to_display:
|
for f in fields_to_display:
|
||||||
@ -326,7 +345,9 @@ def backup_prepare(vms_list = None, exclude_list = None,
|
|||||||
s = ""
|
s = ""
|
||||||
fmt = "{{0:>{0}}} |".format(fields_to_display[0]["width"] + 1)
|
fmt = "{{0:>{0}}} |".format(fields_to_display[0]["width"] + 1)
|
||||||
s += fmt.format("Total size:")
|
s += fmt.format("Total size:")
|
||||||
fmt="{{0:>{0}}} |".format(fields_to_display[1]["width"] + 1 + 2 + fields_to_display[2]["width"] + 1)
|
fmt = "{{0:>{0}}} |".format(
|
||||||
|
fields_to_display[1]["width"] + 1 + 2 + fields_to_display[2][
|
||||||
|
"width"] + 1)
|
||||||
s += fmt.format(size_to_human(total_backup_sz))
|
s += fmt.format(size_to_human(total_backup_sz))
|
||||||
print_callback(s)
|
print_callback(s)
|
||||||
|
|
||||||
@ -336,12 +357,12 @@ 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() if not vm
|
vms_not_for_backup = [vm.name for vm in qvm_collection.values()
|
||||||
.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 QubesException("Please shutdown all VMs before proceeding.")
|
||||||
|
|
||||||
for fileinfo in files_to_backup:
|
for fileinfo in files_to_backup:
|
||||||
@ -350,6 +371,7 @@ def backup_prepare(vms_list = None, exclude_list = None,
|
|||||||
|
|
||||||
return files_to_backup
|
return files_to_backup
|
||||||
|
|
||||||
|
|
||||||
class SendWorker(Process):
|
class SendWorker(Process):
|
||||||
def __init__(self, queue, base_dir, backup_stdout):
|
def __init__(self, queue, base_dir, backup_stdout):
|
||||||
super(SendWorker, self).__init__()
|
super(SendWorker, self).__init__()
|
||||||
@ -377,10 +399,13 @@ class SendWorker(Process):
|
|||||||
tar_final_cmd = ["tar", "-cO", "--posix",
|
tar_final_cmd = ["tar", "-cO", "--posix",
|
||||||
"-C", self.base_dir, filename]
|
"-C", self.base_dir, filename]
|
||||||
final_proc = subprocess.Popen(tar_final_cmd,
|
final_proc = subprocess.Popen(tar_final_cmd,
|
||||||
stdin=subprocess.PIPE, stdout=self.backup_stdout)
|
stdin=subprocess.PIPE,
|
||||||
|
stdout=self.backup_stdout)
|
||||||
if final_proc.wait() >= 2:
|
if final_proc.wait() >= 2:
|
||||||
# handle only exit code 2 (tar fatal error) or greater (call failed?)
|
# handle only exit code 2 (tar fatal error) or
|
||||||
raise QubesException("ERROR: Failed to write the backup, out of disk space? "
|
# greater (call failed?)
|
||||||
|
raise QubesException(
|
||||||
|
"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.")
|
||||||
|
|
||||||
# Delete the file as we don't need it anymore
|
# Delete the file as we don't need it anymore
|
||||||
@ -391,6 +416,7 @@ class SendWorker(Process):
|
|||||||
if BACKUP_DEBUG:
|
if BACKUP_DEBUG:
|
||||||
print "Finished sending thread"
|
print "Finished sending thread"
|
||||||
|
|
||||||
|
|
||||||
def prepare_backup_header(target_directory, passphrase, compressed=False,
|
def prepare_backup_header(target_directory, passphrase, compressed=False,
|
||||||
encrypted=False,
|
encrypted=False,
|
||||||
hmac_algorithm=DEFAULT_HMAC_ALGORITHM,
|
hmac_algorithm=DEFAULT_HMAC_ALGORITHM,
|
||||||
@ -415,7 +441,8 @@ def prepare_backup_header(target_directory, passphrase, compressed=False,
|
|||||||
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 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(base_backup_dir, files_to_backup, passphrase,
|
||||||
progress_callback=None, encrypted=False, appvm=None,
|
progress_callback=None, encrypted=False, appvm=None,
|
||||||
@ -425,8 +452,8 @@ def backup_do(base_backup_dir, files_to_backup, passphrase,
|
|||||||
|
|
||||||
total_backup_sz = 0
|
total_backup_sz = 0
|
||||||
passphrase = passphrase.encode('utf-8')
|
passphrase = passphrase.encode('utf-8')
|
||||||
for file in files_to_backup:
|
for f in files_to_backup:
|
||||||
total_backup_sz += file["size"]
|
total_backup_sz += f["size"]
|
||||||
|
|
||||||
if isinstance(compressed, str):
|
if isinstance(compressed, str):
|
||||||
compression_filter = compressed
|
compression_filter = compressed
|
||||||
@ -435,7 +462,7 @@ def backup_do(base_backup_dir, files_to_backup, passphrase,
|
|||||||
|
|
||||||
running_backup_operation = BackupOperationInfo()
|
running_backup_operation = BackupOperationInfo()
|
||||||
vmproc = None
|
vmproc = None
|
||||||
if appvm != None:
|
if appvm is not None:
|
||||||
# Prepare the backup target (Qubes service call)
|
# Prepare the backup target (Qubes service call)
|
||||||
backup_target = "QUBESRPC qubes.Backup dom0"
|
backup_target = "QUBESRPC qubes.Backup dom0"
|
||||||
|
|
||||||
@ -524,7 +551,8 @@ def backup_do(base_backup_dir, files_to_backup, passphrase,
|
|||||||
|
|
||||||
# The first tar cmd can use any complex feature as we want. Files will
|
# The first tar cmd can use any complex feature as we want. Files will
|
||||||
# be verified before untaring this.
|
# be verified before untaring this.
|
||||||
# Prefix the path in archive with filename["subdir"] to have it verified during untar
|
# Prefix the path in archive with filename["subdir"] to have it
|
||||||
|
# verified during untar
|
||||||
tar_cmdline = ["tar", "-Pc", '--sparse',
|
tar_cmdline = ["tar", "-Pc", '--sparse',
|
||||||
"-f", backup_pipe,
|
"-f", backup_pipe,
|
||||||
'-C', os.path.dirname(filename["path"]),
|
'-C', os.path.dirname(filename["path"]),
|
||||||
@ -534,7 +562,8 @@ def backup_do(base_backup_dir, files_to_backup, passphrase,
|
|||||||
os.path.basename(filename["path"])
|
os.path.basename(filename["path"])
|
||||||
]
|
]
|
||||||
if compressed:
|
if compressed:
|
||||||
tar_cmdline.insert(-1, "--use-compress-program=%s" % compression_filter)
|
tar_cmdline.insert(-1,
|
||||||
|
"--use-compress-program=%s" % compression_filter)
|
||||||
|
|
||||||
if BACKUP_DEBUG:
|
if BACKUP_DEBUG:
|
||||||
print " ".join(tar_cmdline)
|
print " ".join(tar_cmdline)
|
||||||
@ -543,7 +572,9 @@ def backup_do(base_backup_dir, files_to_backup, passphrase,
|
|||||||
# Pipe: tar-sparse | encryptor [| hmac] | tar | backup_target
|
# Pipe: tar-sparse | encryptor [| hmac] | tar | backup_target
|
||||||
# Pipe: tar-sparse [| hmac] | tar | backup_target
|
# Pipe: tar-sparse [| hmac] | tar | backup_target
|
||||||
tar_sparse = subprocess.Popen(tar_cmdline, stdin=subprocess.PIPE,
|
tar_sparse = subprocess.Popen(tar_cmdline, stdin=subprocess.PIPE,
|
||||||
stderr=(open(os.devnull, 'w') if not BACKUP_DEBUG else None))
|
stderr=(open(os.devnull, 'w')
|
||||||
|
if not BACKUP_DEBUG
|
||||||
|
else None))
|
||||||
running_backup_operation.processes_to_kill_on_cancel.append(tar_sparse)
|
running_backup_operation.processes_to_kill_on_cancel.append(tar_sparse)
|
||||||
|
|
||||||
# Wait for compressor (tar) process to finish or for any error of other
|
# Wait for compressor (tar) process to finish or for any error of other
|
||||||
@ -557,7 +588,8 @@ def backup_do(base_backup_dir, files_to_backup, passphrase,
|
|||||||
encryptor = subprocess.Popen(["openssl", "enc",
|
encryptor = subprocess.Popen(["openssl", "enc",
|
||||||
"-e", "-" + crypto_algorithm,
|
"-e", "-" + crypto_algorithm,
|
||||||
"-pass", "pass:" + passphrase],
|
"-pass", "pass:" + passphrase],
|
||||||
stdin=open(backup_pipe,'rb'), stdout=subprocess.PIPE)
|
stdin=open(backup_pipe, 'rb'),
|
||||||
|
stdout=subprocess.PIPE)
|
||||||
pipe = encryptor.stdout
|
pipe = encryptor.stdout
|
||||||
else:
|
else:
|
||||||
pipe = open(backup_pipe, 'rb')
|
pipe = open(backup_pipe, 'rb')
|
||||||
@ -566,7 +598,8 @@ def backup_do(base_backup_dir, files_to_backup, passphrase,
|
|||||||
# Start HMAC
|
# Start HMAC
|
||||||
hmac = subprocess.Popen(["openssl", "dgst",
|
hmac = subprocess.Popen(["openssl", "dgst",
|
||||||
"-" + hmac_algorithm, "-hmac", passphrase],
|
"-" + hmac_algorithm, "-hmac", passphrase],
|
||||||
stdin=subprocess.PIPE, stdout=subprocess.PIPE)
|
stdin=subprocess.PIPE,
|
||||||
|
stdout=subprocess.PIPE)
|
||||||
|
|
||||||
# Prepare a first chunk
|
# Prepare a first chunk
|
||||||
chunkfile = backup_tempfile + "." + "%03d" % i
|
chunkfile = backup_tempfile + "." + "%03d" % i
|
||||||
@ -609,10 +642,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("Failed to write the backup, VM output:\n" +
|
raise QubesException(
|
||||||
|
"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 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
|
||||||
@ -646,7 +680,6 @@ def backup_do(base_backup_dir, files_to_backup, passphrase,
|
|||||||
.poll()
|
.poll()
|
||||||
pipe.close()
|
pipe.close()
|
||||||
|
|
||||||
|
|
||||||
to_send.put("FINISHED")
|
to_send.put("FINISHED")
|
||||||
send_proc.join()
|
send_proc.join()
|
||||||
shutil.rmtree(backup_tmpdir)
|
shutil.rmtree(backup_tmpdir)
|
||||||
@ -658,7 +691,8 @@ 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("Failed to send backup: error in the sending process")
|
raise QubesException(
|
||||||
|
"Failed to send backup: error in the sending process")
|
||||||
|
|
||||||
if vmproc:
|
if vmproc:
|
||||||
if BACKUP_DEBUG:
|
if BACKUP_DEBUG:
|
||||||
@ -678,6 +712,7 @@ def backup_do(base_backup_dir, files_to_backup, passphrase,
|
|||||||
qvm_collection.save()
|
qvm_collection.save()
|
||||||
qvm_collection.unlock_db()
|
qvm_collection.unlock_db()
|
||||||
|
|
||||||
|
|
||||||
'''
|
'''
|
||||||
' Wait for backup chunk to finish
|
' Wait for backup chunk to finish
|
||||||
' - Monitor all the processes (streamproc, hmac, vmproc, addproc) for errors
|
' - Monitor all the processes (streamproc, hmac, vmproc, addproc) for errors
|
||||||
@ -693,16 +728,18 @@ def backup_do(base_backup_dir, files_to_backup, passphrase,
|
|||||||
' "")
|
' "")
|
||||||
' - size_limit is provided and is about to be exceeded
|
' - size_limit is provided and is about to be exceeded
|
||||||
'''
|
'''
|
||||||
def wait_backup_feedback(progress_callback, in_stream, streamproc,
|
|
||||||
backup_target, total_backup_sz, hmac=None, vmproc=None, addproc=None,
|
|
||||||
remove_trailing_bytes=0, size_limit=None):
|
|
||||||
|
|
||||||
|
|
||||||
|
def wait_backup_feedback(progress_callback, in_stream, streamproc,
|
||||||
|
backup_target, total_backup_sz, hmac=None, vmproc=None,
|
||||||
|
addproc=None,
|
||||||
|
remove_trailing_bytes=0, size_limit=None):
|
||||||
buffer_size = 409600
|
buffer_size = 409600
|
||||||
|
|
||||||
run_error = None
|
run_error = None
|
||||||
run_count = 1
|
run_count = 1
|
||||||
bytes_copied = 0
|
bytes_copied = 0
|
||||||
while run_count > 0 and run_error == None:
|
while run_count > 0 and run_error is None:
|
||||||
|
|
||||||
if size_limit and bytes_copied + buffer_size > size_limit:
|
if size_limit and bytes_copied + buffer_size > size_limit:
|
||||||
return "size_limit"
|
return "size_limit"
|
||||||
@ -713,7 +750,7 @@ def wait_backup_feedback(progress_callback, in_stream, streamproc,
|
|||||||
run_count = 0
|
run_count = 0
|
||||||
if hmac:
|
if hmac:
|
||||||
retcode = hmac.poll()
|
retcode = hmac.poll()
|
||||||
if retcode != None:
|
if retcode is not None:
|
||||||
if retcode != 0:
|
if retcode != 0:
|
||||||
run_error = "hmac"
|
run_error = "hmac"
|
||||||
else:
|
else:
|
||||||
@ -721,7 +758,7 @@ def wait_backup_feedback(progress_callback, in_stream, streamproc,
|
|||||||
|
|
||||||
if addproc:
|
if addproc:
|
||||||
retcode = addproc.poll()
|
retcode = addproc.poll()
|
||||||
if retcode != None:
|
if retcode is not None:
|
||||||
if retcode != 0:
|
if retcode != 0:
|
||||||
run_error = "addproc"
|
run_error = "addproc"
|
||||||
else:
|
else:
|
||||||
@ -729,7 +766,7 @@ def wait_backup_feedback(progress_callback, in_stream, streamproc,
|
|||||||
|
|
||||||
if vmproc:
|
if vmproc:
|
||||||
retcode = vmproc.poll()
|
retcode = vmproc.poll()
|
||||||
if retcode != None:
|
if retcode is not None:
|
||||||
if retcode != 0:
|
if retcode != 0:
|
||||||
run_error = "VM"
|
run_error = "VM"
|
||||||
if BACKUP_DEBUG:
|
if BACKUP_DEBUG:
|
||||||
@ -740,7 +777,7 @@ def wait_backup_feedback(progress_callback, in_stream, streamproc,
|
|||||||
|
|
||||||
if streamproc:
|
if streamproc:
|
||||||
retcode = streamproc.poll()
|
retcode = streamproc.poll()
|
||||||
if retcode != None:
|
if retcode is not None:
|
||||||
if retcode != 0:
|
if retcode != 0:
|
||||||
run_error = "streamproc"
|
run_error = "streamproc"
|
||||||
break
|
break
|
||||||
@ -765,13 +802,14 @@ def wait_backup_feedback(progress_callback, in_stream, streamproc,
|
|||||||
|
|
||||||
return run_error
|
return run_error
|
||||||
|
|
||||||
|
|
||||||
def verify_hmac(filename, hmacfile, passphrase, algorithm):
|
def verify_hmac(filename, hmacfile, passphrase, algorithm):
|
||||||
if BACKUP_DEBUG:
|
if BACKUP_DEBUG:
|
||||||
print "Verifying file " + filename
|
print "Verifying file " + filename
|
||||||
|
|
||||||
if hmacfile != filename + ".hmac":
|
if hmacfile != filename + ".hmac":
|
||||||
raise QubesException(
|
raise QubesException(
|
||||||
"ERROR: expected hmac for {}, but got {}".\
|
"ERROR: expected hmac for {}, but got {}".
|
||||||
format(filename, hmacfile))
|
format(filename, hmacfile))
|
||||||
|
|
||||||
hmac_proc = subprocess.Popen(["openssl", "dgst", "-" + algorithm,
|
hmac_proc = subprocess.Popen(["openssl", "dgst", "-" + algorithm,
|
||||||
@ -781,7 +819,8 @@ 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("ERROR: verify file {0}: {1}".format(filename, hmac_stderr))
|
raise QubesException(
|
||||||
|
"ERROR: verify file {0}: {1}".format(filename, hmac_stderr))
|
||||||
else:
|
else:
|
||||||
if BACKUP_DEBUG:
|
if BACKUP_DEBUG:
|
||||||
print "Loading hmac for file " + filename
|
print "Loading hmac for file " + filename
|
||||||
@ -794,8 +833,8 @@ def verify_hmac(filename, hmacfile, passphrase, algorithm):
|
|||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
raise QubesException(
|
raise 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)))
|
||||||
# Not reachable
|
# Not reachable
|
||||||
return False
|
return False
|
||||||
@ -901,12 +940,12 @@ class ExtractWorker2(Process):
|
|||||||
|
|
||||||
if filename.endswith('.000'):
|
if filename.endswith('.000'):
|
||||||
# next file
|
# next file
|
||||||
if self.tar2_process != None:
|
if self.tar2_process is not None:
|
||||||
if self.tar2_process.wait() != 0:
|
if self.tar2_process.wait() != 0:
|
||||||
self.collect_tar_output()
|
self.collect_tar_output()
|
||||||
self.error_callback(
|
self.error_callback(
|
||||||
"ERROR: unable to extract files for {0}, tar "
|
"ERROR: unable to extract files for {0}, tar "
|
||||||
"output:\n {1}".\
|
"output:\n {1}".
|
||||||
format(self.tar2_current_file,
|
format(self.tar2_current_file,
|
||||||
"\n ".join(self.tar2_stderr)))
|
"\n ".join(self.tar2_stderr)))
|
||||||
else:
|
else:
|
||||||
@ -953,9 +992,12 @@ class ExtractWorker2(Process):
|
|||||||
}
|
}
|
||||||
if self.encrypted:
|
if self.encrypted:
|
||||||
# Start decrypt
|
# Start decrypt
|
||||||
self.decryptor_process = subprocess.Popen (["openssl", "enc",
|
self.decryptor_process = subprocess.Popen(
|
||||||
"-d", "-" + self.crypto_algorithm,
|
["openssl", "enc",
|
||||||
"-pass", "pass:"+self.passphrase] +
|
"-d",
|
||||||
|
"-" + self.crypto_algorithm,
|
||||||
|
"-pass",
|
||||||
|
"pass:" + self.passphrase] +
|
||||||
(["-z"] if self.compressed else []),
|
(["-z"] if self.compressed else []),
|
||||||
stdin=open(filename, 'rb'),
|
stdin=open(filename, 'rb'),
|
||||||
stdout=subprocess.PIPE)
|
stdout=subprocess.PIPE)
|
||||||
@ -966,7 +1008,8 @@ class ExtractWorker2(Process):
|
|||||||
streamproc=self.decryptor_process,
|
streamproc=self.decryptor_process,
|
||||||
**common_args)
|
**common_args)
|
||||||
elif self.compressed:
|
elif self.compressed:
|
||||||
self.decompressor_process = subprocess.Popen (["gzip", "-d"],
|
self.decompressor_process = subprocess.Popen(
|
||||||
|
["gzip", "-d"],
|
||||||
stdin=open(filename, 'rb'),
|
stdin=open(filename, 'rb'),
|
||||||
stdout=subprocess.PIPE)
|
stdout=subprocess.PIPE)
|
||||||
|
|
||||||
@ -986,7 +1029,9 @@ class ExtractWorker2(Process):
|
|||||||
except IOError as e:
|
except IOError as e:
|
||||||
if e.errno == errno.EPIPE:
|
if e.errno == errno.EPIPE:
|
||||||
if BACKUP_DEBUG:
|
if BACKUP_DEBUG:
|
||||||
self.error_callback("Got EPIPE while closing pipe to the inner tar process")
|
self.error_callback(
|
||||||
|
"Got EPIPE while closing pipe to "
|
||||||
|
"the inner tar process")
|
||||||
# ignore the error
|
# ignore the error
|
||||||
else:
|
else:
|
||||||
raise
|
raise
|
||||||
@ -999,7 +1044,7 @@ class ExtractWorker2(Process):
|
|||||||
self.tar2_process.terminate()
|
self.tar2_process.terminate()
|
||||||
self.tar2_process.wait()
|
self.tar2_process.wait()
|
||||||
self.tar2_process = None
|
self.tar2_process = None
|
||||||
self.error_callback("Error while processing '%s': %s " % \
|
self.error_callback("Error while processing '%s': %s " %
|
||||||
(self.tar2_current_file, details))
|
(self.tar2_current_file, details))
|
||||||
|
|
||||||
# Delete the file as we don't need it anymore
|
# Delete the file as we don't need it anymore
|
||||||
@ -1029,9 +1074,8 @@ class ExtractWorker2(Process):
|
|||||||
if BACKUP_DEBUG and callable(self.print_callback):
|
if BACKUP_DEBUG and callable(self.print_callback):
|
||||||
self.print_callback("Finished extracting thread")
|
self.print_callback("Finished extracting thread")
|
||||||
|
|
||||||
|
|
||||||
class ExtractWorker3(ExtractWorker2):
|
class ExtractWorker3(ExtractWorker2):
|
||||||
|
|
||||||
|
|
||||||
def __init__(self, queue, base_dir, passphrase, encrypted, total_size,
|
def __init__(self, queue, base_dir, passphrase, encrypted, total_size,
|
||||||
print_callback, error_callback, progress_callback, vmproc=None,
|
print_callback, error_callback, progress_callback, vmproc=None,
|
||||||
compressed=False, crypto_algorithm=DEFAULT_CRYPTO_ALGORITHM,
|
compressed=False, crypto_algorithm=DEFAULT_CRYPTO_ALGORITHM,
|
||||||
@ -1063,13 +1107,13 @@ class ExtractWorker3(ExtractWorker2):
|
|||||||
|
|
||||||
if filename.endswith('.000'):
|
if filename.endswith('.000'):
|
||||||
# next file
|
# next file
|
||||||
if self.tar2_process != None:
|
if self.tar2_process is not None:
|
||||||
input_pipe.close()
|
input_pipe.close()
|
||||||
if self.tar2_process.wait() != 0:
|
if self.tar2_process.wait() != 0:
|
||||||
self.collect_tar_output()
|
self.collect_tar_output()
|
||||||
self.error_callback(
|
self.error_callback(
|
||||||
"ERROR: unable to extract files for {0}, tar "
|
"ERROR: unable to extract files for {0}, tar "
|
||||||
"output:\n {1}".\
|
"output:\n {1}".
|
||||||
format(self.tar2_current_file,
|
format(self.tar2_current_file,
|
||||||
"\n ".join(self.tar2_stderr)))
|
"\n ".join(self.tar2_stderr)))
|
||||||
else:
|
else:
|
||||||
@ -1095,18 +1139,23 @@ class ExtractWorker3(ExtractWorker2):
|
|||||||
unicode(tar2_cmdline))
|
unicode(tar2_cmdline))
|
||||||
if self.encrypted:
|
if self.encrypted:
|
||||||
# Start decrypt
|
# Start decrypt
|
||||||
self.decryptor_process = subprocess.Popen (["openssl", "enc",
|
self.decryptor_process = subprocess.Popen(
|
||||||
"-d", "-" + self.crypto_algorithm,
|
["openssl", "enc",
|
||||||
"-pass", "pass:"+self.passphrase],
|
"-d",
|
||||||
|
"-" + self.crypto_algorithm,
|
||||||
|
"-pass",
|
||||||
|
"pass:" + self.passphrase],
|
||||||
stdin=subprocess.PIPE,
|
stdin=subprocess.PIPE,
|
||||||
stdout=subprocess.PIPE)
|
stdout=subprocess.PIPE)
|
||||||
|
|
||||||
self.tar2_process = subprocess.Popen(tar2_cmdline,
|
self.tar2_process = subprocess.Popen(
|
||||||
|
tar2_cmdline,
|
||||||
stdin=self.decryptor_process.stdout,
|
stdin=self.decryptor_process.stdout,
|
||||||
stderr=subprocess.PIPE)
|
stderr=subprocess.PIPE)
|
||||||
input_pipe = self.decryptor_process.stdin
|
input_pipe = self.decryptor_process.stdin
|
||||||
else:
|
else:
|
||||||
self.tar2_process = subprocess.Popen(tar2_cmdline,
|
self.tar2_process = subprocess.Popen(
|
||||||
|
tar2_cmdline,
|
||||||
stdin=subprocess.PIPE,
|
stdin=subprocess.PIPE,
|
||||||
stderr=subprocess.PIPE)
|
stderr=subprocess.PIPE)
|
||||||
input_pipe = self.tar2_process.stdin
|
input_pipe = self.tar2_process.stdin
|
||||||
@ -1152,7 +1201,7 @@ class ExtractWorker3(ExtractWorker2):
|
|||||||
self.tar2_process.terminate()
|
self.tar2_process.terminate()
|
||||||
self.tar2_process.wait()
|
self.tar2_process.wait()
|
||||||
self.tar2_process = None
|
self.tar2_process = None
|
||||||
self.error_callback("Error while processing '%s': %s " % \
|
self.error_callback("Error while processing '%s': %s " %
|
||||||
(self.tar2_current_file, details))
|
(self.tar2_current_file, details))
|
||||||
|
|
||||||
# Delete the file as we don't need it anymore
|
# Delete the file as we don't need it anymore
|
||||||
@ -1198,6 +1247,7 @@ def get_supported_hmac_algo(hmac_algorithm):
|
|||||||
yield algo.strip()
|
yield algo.strip()
|
||||||
proc.wait()
|
proc.wait()
|
||||||
|
|
||||||
|
|
||||||
def parse_backup_header(filename):
|
def parse_backup_header(filename):
|
||||||
header_data = {}
|
header_data = {}
|
||||||
with open(filename, 'r') as f:
|
with open(filename, 'r') as f:
|
||||||
@ -1216,31 +1266,35 @@ def parse_backup_header(filename):
|
|||||||
header_data[key] = value
|
header_data[key] = value
|
||||||
return header_data
|
return header_data
|
||||||
|
|
||||||
|
|
||||||
def restore_vm_dirs(backup_source, restore_tmpdir, passphrase, vms_dirs, vms,
|
def restore_vm_dirs(backup_source, restore_tmpdir, passphrase, vms_dirs, vms,
|
||||||
vms_size, print_callback=None, error_callback=None,
|
vms_size, print_callback=None, error_callback=None,
|
||||||
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,
|
||||||
verify_only=False, format_version = CURRENT_BACKUP_FORMAT_VERSION,
|
verify_only=False,
|
||||||
|
format_version=CURRENT_BACKUP_FORMAT_VERSION,
|
||||||
compression_filter=None):
|
compression_filter=None):
|
||||||
|
|
||||||
global running_backup_operation
|
global running_backup_operation
|
||||||
|
|
||||||
if callable(print_callback):
|
if callable(print_callback):
|
||||||
if BACKUP_DEBUG:
|
if BACKUP_DEBUG:
|
||||||
print_callback("Working in temporary dir:" + restore_tmpdir)
|
print_callback("Working in temporary dir:" + restore_tmpdir)
|
||||||
print_callback("Extracting data: " + size_to_human(vms_size)+" to restore")
|
print_callback(
|
||||||
|
"Extracting data: " + size_to_human(vms_size) + " to restore")
|
||||||
|
|
||||||
passphrase = passphrase.encode('utf-8')
|
passphrase = passphrase.encode('utf-8')
|
||||||
header_data = None
|
header_data = None
|
||||||
vmproc = None
|
vmproc = None
|
||||||
if appvm != None:
|
if appvm is not None:
|
||||||
# Prepare the backup target (Qubes service call)
|
# Prepare the backup target (Qubes service call)
|
||||||
backup_target = "QUBESRPC qubes.Restore dom0"
|
backup_target = "QUBESRPC qubes.Restore dom0"
|
||||||
|
|
||||||
# If APPVM, STDOUT is a PIPE
|
# If APPVM, STDOUT is a PIPE
|
||||||
vmproc = appvm.run(command = backup_target, passio_popen = True, passio_stderr=True)
|
vmproc = appvm.run(command=backup_target, passio_popen=True,
|
||||||
vmproc.stdin.write(backup_source.replace("\r","").replace("\n","")+"\n")
|
passio_stderr=True)
|
||||||
|
vmproc.stdin.write(
|
||||||
|
backup_source.replace("\r", "").replace("\n", "") + "\n")
|
||||||
|
|
||||||
# Send to tar2qfile the VMs that should be extracted
|
# Send to tar2qfile the VMs that should be extracted
|
||||||
vmproc.stdin.write(" ".join(vms_dirs) + "\n")
|
vmproc.stdin.write(" ".join(vms_dirs) + "\n")
|
||||||
@ -1271,10 +1325,12 @@ def restore_vm_dirs (backup_source, restore_tmpdir, passphrase, vms_dirs, vms,
|
|||||||
# chunks. Additionally each file have own hmac file. So assume upper
|
# chunks. Additionally each file have own hmac file. So assume upper
|
||||||
# limit as 2*(10*COUNT_OF_VMS+TOTAL_SIZE/100MB)
|
# limit as 2*(10*COUNT_OF_VMS+TOTAL_SIZE/100MB)
|
||||||
tar1_env['UPDATES_MAX_FILES'] = str(2 * (10 * len(vms_dirs) +
|
tar1_env['UPDATES_MAX_FILES'] = str(2 * (10 * len(vms_dirs) +
|
||||||
int(vms_size/(100*1024*1024))))
|
int(vms_size /
|
||||||
|
(100 * 1024 * 1024))))
|
||||||
if BACKUP_DEBUG and callable(print_callback):
|
if BACKUP_DEBUG and callable(print_callback):
|
||||||
print_callback("Run command" + unicode(tar1_command))
|
print_callback("Run command" + unicode(tar1_command))
|
||||||
command = subprocess.Popen(tar1_command,
|
command = subprocess.Popen(
|
||||||
|
tar1_command,
|
||||||
stdin=backup_stdin,
|
stdin=backup_stdin,
|
||||||
stdout=vmproc.stdin if vmproc else subprocess.PIPE,
|
stdout=vmproc.stdin if vmproc else subprocess.PIPE,
|
||||||
stderr=subprocess.PIPE,
|
stderr=subprocess.PIPE,
|
||||||
@ -1348,7 +1404,8 @@ def restore_vm_dirs (backup_source, restore_tmpdir, passphrase, vms_dirs, vms,
|
|||||||
if BackupHeader.encrypted in header_data:
|
if BackupHeader.encrypted in header_data:
|
||||||
encrypted = header_data[BackupHeader.encrypted]
|
encrypted = header_data[BackupHeader.encrypted]
|
||||||
if BackupHeader.compression_filter in header_data:
|
if BackupHeader.compression_filter in header_data:
|
||||||
compression_filter = header_data[BackupHeader.compression_filter]
|
compression_filter = header_data[
|
||||||
|
BackupHeader.compression_filter]
|
||||||
os.unlink(filename)
|
os.unlink(filename)
|
||||||
else:
|
else:
|
||||||
# if no header found, create one with guessed HMAC algo
|
# if no header found, create one with guessed HMAC algo
|
||||||
@ -1383,7 +1440,8 @@ def restore_vm_dirs (backup_source, restore_tmpdir, passphrase, vms_dirs, vms,
|
|||||||
extractor_params['compression_filter'] = compression_filter
|
extractor_params['compression_filter'] = compression_filter
|
||||||
extract_proc = ExtractWorker3(**extractor_params)
|
extract_proc = ExtractWorker3(**extractor_params)
|
||||||
else:
|
else:
|
||||||
raise NotImplemented("Backup format version %d not supported" % format_version)
|
raise NotImplemented(
|
||||||
|
"Backup format version %d not supported" % format_version)
|
||||||
extract_proc.start()
|
extract_proc.start()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -1416,15 +1474,16 @@ def restore_vm_dirs (backup_source, restore_tmpdir, passphrase, vms_dirs, vms,
|
|||||||
if running_backup_operation and running_backup_operation.canceled:
|
if running_backup_operation and running_backup_operation.canceled:
|
||||||
break
|
break
|
||||||
# if reading archive directly with tar, wait for next filename -
|
# if reading archive directly with tar, wait for next filename -
|
||||||
# tar prints filename before processing it, so wait for the next one to be
|
# tar prints filename before processing it, so wait for
|
||||||
# sure that whole file was extracted
|
# the next one to be sure that whole file was extracted
|
||||||
if not appvm:
|
if not appvm:
|
||||||
nextfile = filelist_pipe.readline().strip()
|
nextfile = filelist_pipe.readline().strip()
|
||||||
|
|
||||||
if BACKUP_DEBUG and callable(print_callback):
|
if BACKUP_DEBUG and callable(print_callback):
|
||||||
print_callback("Getting hmac:" + hmacfile)
|
print_callback("Getting hmac:" + hmacfile)
|
||||||
if not hmacfile or hmacfile == "EOF":
|
if not hmacfile or hmacfile == "EOF":
|
||||||
# Premature end of archive, either of tar1_command or vmproc exited with error
|
# Premature end of archive, either of tar1_command or
|
||||||
|
# vmproc exited with error
|
||||||
break
|
break
|
||||||
|
|
||||||
if not any(map(lambda x: filename.startswith(x), vms_dirs)):
|
if not any(map(lambda x: filename.startswith(x), vms_dirs)):
|
||||||
@ -1445,17 +1504,18 @@ def restore_vm_dirs (backup_source, restore_tmpdir, passphrase, vms_dirs, vms,
|
|||||||
|
|
||||||
if command.wait() != 0 and not expect_tar_error:
|
if command.wait() != 0 and not expect_tar_error:
|
||||||
raise QubesException(
|
raise 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 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("Premature end of archive, the last file was %s" % filename)
|
raise QubesException(
|
||||||
|
"Premature end of archive, the last file was %s" % filename)
|
||||||
except:
|
except:
|
||||||
to_extract.put("ERROR")
|
to_extract.put("ERROR")
|
||||||
extract_proc.join()
|
extract_proc.join()
|
||||||
@ -1467,15 +1527,16 @@ def restore_vm_dirs (backup_source, restore_tmpdir, passphrase, vms_dirs, vms,
|
|||||||
print_callback("Waiting for the extraction process to finish...")
|
print_callback("Waiting for the extraction process to finish...")
|
||||||
extract_proc.join()
|
extract_proc.join()
|
||||||
if BACKUP_DEBUG and callable(print_callback):
|
if BACKUP_DEBUG and callable(print_callback):
|
||||||
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 QubesException(
|
||||||
"unable to extract the qubes backup. " \
|
"unable to extract the qubes backup. "
|
||||||
"Check extracting process errors.")
|
"Check extracting process errors.")
|
||||||
|
|
||||||
return header_data
|
return header_data
|
||||||
|
|
||||||
|
|
||||||
def backup_restore_set_defaults(options):
|
def backup_restore_set_defaults(options):
|
||||||
if 'use-default-netvm' not in options:
|
if 'use-default-netvm' not in options:
|
||||||
options['use-default-netvm'] = False
|
options['use-default-netvm'] = False
|
||||||
@ -1494,6 +1555,7 @@ def backup_restore_set_defaults(options):
|
|||||||
|
|
||||||
return options
|
return options
|
||||||
|
|
||||||
|
|
||||||
def load_hmac(hmac):
|
def load_hmac(hmac):
|
||||||
hmac = hmac.strip().split("=")
|
hmac = hmac.strip().split("=")
|
||||||
if len(hmac) > 1:
|
if len(hmac) > 1:
|
||||||
@ -1503,6 +1565,7 @@ def load_hmac(hmac):
|
|||||||
|
|
||||||
return hmac
|
return hmac
|
||||||
|
|
||||||
|
|
||||||
def backup_detect_format_version(backup_location):
|
def backup_detect_format_version(backup_location):
|
||||||
if os.path.exists(os.path.join(backup_location, 'qubes.xml')):
|
if os.path.exists(os.path.join(backup_location, 'qubes.xml')):
|
||||||
return 1
|
return 1
|
||||||
@ -1511,23 +1574,25 @@ def backup_detect_format_version(backup_location):
|
|||||||
# is read
|
# is read
|
||||||
return 2
|
return 2
|
||||||
|
|
||||||
|
|
||||||
def backup_restore_header(source, passphrase,
|
def backup_restore_header(source, passphrase,
|
||||||
print_callback = print_stdout, error_callback = print_stderr,
|
print_callback=print_stdout,
|
||||||
encrypted=False, appvm=None, compressed = False, format_version = None,
|
error_callback=print_stderr,
|
||||||
|
encrypted=False, appvm=None, compressed=False,
|
||||||
|
format_version=None,
|
||||||
hmac_algorithm=DEFAULT_HMAC_ALGORITHM,
|
hmac_algorithm=DEFAULT_HMAC_ALGORITHM,
|
||||||
crypto_algorithm=DEFAULT_CRYPTO_ALGORITHM):
|
crypto_algorithm=DEFAULT_CRYPTO_ALGORITHM):
|
||||||
|
|
||||||
global running_backup_operation
|
global running_backup_operation
|
||||||
vmproc = None
|
vmproc = None
|
||||||
running_backup_operation = None
|
running_backup_operation = None
|
||||||
|
|
||||||
restore_tmpdir = tempfile.mkdtemp(prefix="/var/tmp/restore_")
|
restore_tmpdir = tempfile.mkdtemp(prefix="/var/tmp/restore_")
|
||||||
|
|
||||||
if format_version == None:
|
if format_version is None:
|
||||||
format_version = backup_detect_format_version(source)
|
format_version = backup_detect_format_version(source)
|
||||||
|
|
||||||
if format_version == 1:
|
if format_version == 1:
|
||||||
return (restore_tmpdir, os.path.join(source, 'qubes.xml'), None)
|
return restore_tmpdir, os.path.join(source, 'qubes.xml'), None
|
||||||
|
|
||||||
# tar2qfile matches only beginnings, while tar full path
|
# tar2qfile matches only beginnings, while tar full path
|
||||||
if appvm:
|
if appvm:
|
||||||
@ -1555,6 +1620,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 restore_info_verify(restore_info, host_collection):
|
def restore_info_verify(restore_info, host_collection):
|
||||||
options = restore_info['$OPTIONS$']
|
options = restore_info['$OPTIONS$']
|
||||||
for vm in restore_info.keys():
|
for vm in restore_info.keys():
|
||||||
@ -1601,7 +1667,7 @@ def restore_info_verify(restore_info, host_collection):
|
|||||||
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()):
|
||||||
|
|
||||||
# Maybe the (custom) netvm is in the backup?
|
# Maybe the (custom) netvm is in the backup?
|
||||||
if not (netvm_name in restore_info.keys() and \
|
if not (netvm_name in restore_info.keys() and
|
||||||
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 \
|
||||||
@ -1620,10 +1686,13 @@ def restore_info_verify(restore_info, host_collection):
|
|||||||
|
|
||||||
return restore_info
|
return restore_info
|
||||||
|
|
||||||
|
|
||||||
def backup_restore_prepare(backup_location, passphrase, options=None,
|
def backup_restore_prepare(backup_location, passphrase, options=None,
|
||||||
host_collection=None, encrypted=False, appvm=None,
|
host_collection=None, encrypted=False, appvm=None,
|
||||||
compressed = False, print_callback = print_stdout, error_callback = print_stderr,
|
compressed=False, print_callback=print_stdout,
|
||||||
format_version=None, hmac_algorithm=DEFAULT_HMAC_ALGORITHM,
|
error_callback=print_stderr,
|
||||||
|
format_version=None,
|
||||||
|
hmac_algorithm=DEFAULT_HMAC_ALGORITHM,
|
||||||
crypto_algorithm=DEFAULT_CRYPTO_ALGORITHM):
|
crypto_algorithm=DEFAULT_CRYPTO_ALGORITHM):
|
||||||
if options is None:
|
if options is None:
|
||||||
options = {}
|
options = {}
|
||||||
@ -1633,17 +1702,19 @@ def backup_restore_prepare(backup_location, passphrase, options = None,
|
|||||||
# so no need for fallback in function parameter
|
# so no need for fallback in function parameter
|
||||||
compression_filter = DEFAULT_COMPRESSION_FILTER
|
compression_filter = DEFAULT_COMPRESSION_FILTER
|
||||||
|
|
||||||
#### Private functions begin
|
# Private functions begin
|
||||||
def is_vm_included_in_backup_v1(backup_dir, vm):
|
def is_vm_included_in_backup_v1(backup_dir, vm):
|
||||||
if vm.qid == 0:
|
if vm.qid == 0:
|
||||||
return os.path.exists(os.path.join(backup_dir, 'dom0-home'))
|
return os.path.exists(os.path.join(backup_dir, 'dom0-home'))
|
||||||
|
|
||||||
backup_vm_dir_path = vm.dir_path.replace (system_path["qubes_base_dir"], backup_dir)
|
backup_vm_dir_path = vm.dir_path.replace(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
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def is_vm_included_in_backup_v2(backup_dir, vm):
|
def is_vm_included_in_backup_v2(backup_dir, vm):
|
||||||
if vm.backup_content:
|
if vm.backup_content:
|
||||||
return True
|
return True
|
||||||
@ -1658,7 +1729,8 @@ def backup_restore_prepare(backup_location, passphrase, options = None,
|
|||||||
return m.group(2)
|
return m.group(2)
|
||||||
|
|
||||||
return template
|
return template
|
||||||
#### Private functions end
|
|
||||||
|
# Private functions end
|
||||||
|
|
||||||
# Format versions:
|
# Format versions:
|
||||||
# 1 - Qubes R1, Qubes R2 beta1, beta2
|
# 1 - Qubes R1, Qubes R2 beta1, beta2
|
||||||
@ -1675,10 +1747,10 @@ def backup_restore_prepare(backup_location, passphrase, options = None,
|
|||||||
if not os.path.isfile(backup_location):
|
if not os.path.isfile(backup_location):
|
||||||
raise QubesException("Invalid backup location (not a file or "
|
raise QubesException("Invalid backup location (not a file or "
|
||||||
"directory with qubes.xml)"
|
"directory with qubes.xml)"
|
||||||
": %s" % unicode(
|
": %s" % unicode(backup_location))
|
||||||
backup_location))
|
|
||||||
else:
|
else:
|
||||||
raise QubesException("Unknown backup format version: %s" % str(format_version))
|
raise QubesException(
|
||||||
|
"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(
|
||||||
backup_location,
|
backup_location,
|
||||||
@ -1736,7 +1808,8 @@ def backup_restore_prepare(backup_location, passphrase, options = None,
|
|||||||
if vm.template is None:
|
if vm.template is None:
|
||||||
vms_to_restore[vm.name]['template'] = None
|
vms_to_restore[vm.name]['template'] = None
|
||||||
else:
|
else:
|
||||||
templatevm_name = find_template_name(vm.template.name, options['replace-template'])
|
templatevm_name = find_template_name(vm.template.name, options[
|
||||||
|
'replace-template'])
|
||||||
vms_to_restore[vm.name]['template'] = templatevm_name
|
vms_to_restore[vm.name]['template'] = templatevm_name
|
||||||
|
|
||||||
if vm.netvm is None:
|
if vm.netvm is None:
|
||||||
@ -1797,6 +1870,7 @@ def backup_restore_prepare(backup_location, passphrase, options = None,
|
|||||||
os.unlink(qubes_xml)
|
os.unlink(qubes_xml)
|
||||||
return vms_to_restore
|
return vms_to_restore
|
||||||
|
|
||||||
|
|
||||||
def backup_restore_print_summary(restore_info, print_callback=print_stdout):
|
def backup_restore_print_summary(restore_info, print_callback=print_stdout):
|
||||||
fields = {
|
fields = {
|
||||||
"qid": {"func": "vm.qid"},
|
"qid": {"func": "vm.qid"},
|
||||||
@ -1860,7 +1934,7 @@ def backup_restore_print_summary(restore_info, print_callback = print_stdout):
|
|||||||
|
|
||||||
for vm_info in restore_info.values():
|
for vm_info in restore_info.values():
|
||||||
# Skip non-VM here
|
# Skip non-VM here
|
||||||
if not 'vm' in vm_info:
|
if 'vm' not in vm_info:
|
||||||
continue
|
continue
|
||||||
vm = vm_info['vm']
|
vm = vm_info['vm']
|
||||||
s = ""
|
s = ""
|
||||||
@ -1898,17 +1972,18 @@ def backup_restore_print_summary(restore_info, print_callback = print_stdout):
|
|||||||
|
|
||||||
print_callback(s)
|
print_callback(s)
|
||||||
|
|
||||||
|
|
||||||
def backup_restore_do(restore_info,
|
def backup_restore_do(restore_info,
|
||||||
host_collection=None, print_callback=print_stdout,
|
host_collection=None, print_callback=print_stdout,
|
||||||
error_callback=print_stderr, progress_callback=None,
|
error_callback=print_stderr, progress_callback=None,
|
||||||
):
|
):
|
||||||
|
|
||||||
global running_backup_operation
|
global running_backup_operation
|
||||||
|
|
||||||
### 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_dir)
|
backup_src_dir = src_dir.replace(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
|
||||||
retcode = subprocess.call(["cp", "-rp", backup_src_dir, dst_dir])
|
retcode = subprocess.call(["cp", "-rp", backup_src_dir, dst_dir])
|
||||||
@ -1916,7 +1991,8 @@ def backup_restore_do(restore_info,
|
|||||||
raise QubesException(
|
raise QubesException(
|
||||||
"*** Error while copying file {0} to {1}".format(backup_src_dir,
|
"*** Error while copying file {0} to {1}".format(backup_src_dir,
|
||||||
dst_dir))
|
dst_dir))
|
||||||
### Private functions end
|
|
||||||
|
# Private functions end
|
||||||
|
|
||||||
options = restore_info['$OPTIONS$']
|
options = restore_info['$OPTIONS$']
|
||||||
backup_location = options['location']
|
backup_location = options['location']
|
||||||
@ -1986,7 +2062,8 @@ def backup_restore_do(restore_info,
|
|||||||
raise
|
raise
|
||||||
else:
|
else:
|
||||||
if callable(print_callback):
|
if callable(print_callback):
|
||||||
print_callback("Some errors occurred during data extraction, "
|
print_callback(
|
||||||
|
"Some errors occurred during data extraction, "
|
||||||
"continuing anyway to restore at least some "
|
"continuing anyway to restore at least some "
|
||||||
"VMs")
|
"VMs")
|
||||||
else:
|
else:
|
||||||
@ -2012,8 +2089,10 @@ def backup_restore_do(restore_info,
|
|||||||
if not vm.__class__ == vm_class:
|
if not vm.__class__ == vm_class:
|
||||||
continue
|
continue
|
||||||
if callable(print_callback):
|
if callable(print_callback):
|
||||||
print_callback("-> Restoring {type} {0}...".format(vm.name, type=vm_class_name))
|
print_callback("-> Restoring {type} {0}...".
|
||||||
retcode = subprocess.call (["mkdir", "-p", os.path.dirname(vm.dir_path)])
|
format(vm.name, type=vm_class_name))
|
||||||
|
retcode = subprocess.call(
|
||||||
|
["mkdir", "-p", os.path.dirname(vm.dir_path)])
|
||||||
if retcode != 0:
|
if retcode != 0:
|
||||||
error_callback("*** Cannot create directory: {0}?!".format(
|
error_callback("*** Cannot create directory: {0}?!".format(
|
||||||
vm.dir_path))
|
vm.dir_path))
|
||||||
@ -2054,8 +2133,8 @@ def backup_restore_do(restore_info,
|
|||||||
# defined - accessing it touches non-existent '_kernel'
|
# defined - accessing it touches non-existent '_kernel'
|
||||||
if not isinstance(vm, QubesVmClasses['QubesHVm']):
|
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 os.listdir(system_path[
|
if vm.kernel and vm.kernel not in \
|
||||||
'qubes_kernels_base_dir']):
|
os.listdir(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)
|
||||||
@ -2071,7 +2150,8 @@ def backup_restore_do(restore_info,
|
|||||||
new_vm.appmenus_create(verbose=callable(print_callback))
|
new_vm.appmenus_create(verbose=callable(print_callback))
|
||||||
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("*** VM '{0}' will not have appmenus".format(vm.name))
|
error_callback(
|
||||||
|
"*** VM '{0}' will not have appmenus".format(vm.name))
|
||||||
|
|
||||||
# Set network dependencies - only non-default netvm setting
|
# Set network dependencies - only non-default netvm setting
|
||||||
for vm in vms.values():
|
for vm in vms.values():
|
||||||
@ -2098,7 +2178,6 @@ def backup_restore_do(restore_info,
|
|||||||
else:
|
else:
|
||||||
raise BackupCanceledError("Restore canceled")
|
raise BackupCanceledError("Restore canceled")
|
||||||
|
|
||||||
|
|
||||||
# ... and dom0 home as last step
|
# ... and dom0 home as last step
|
||||||
if 'dom0' in restore_info.keys() and restore_info['dom0']['good-to-go']:
|
if 'dom0' in restore_info.keys() and restore_info['dom0']['good-to-go']:
|
||||||
backup_path = restore_info['dom0']['subdir']
|
backup_path = restore_info['dom0']['subdir']
|
||||||
@ -2108,18 +2187,24 @@ def backup_restore_do(restore_info,
|
|||||||
backup_dom0_home_dir = os.path.join(backup_location, backup_path)
|
backup_dom0_home_dir = os.path.join(backup_location, backup_path)
|
||||||
else:
|
else:
|
||||||
backup_dom0_home_dir = os.path.join(restore_tmpdir, backup_path)
|
backup_dom0_home_dir = os.path.join(restore_tmpdir, backup_path)
|
||||||
restore_home_backupdir = "home-pre-restore-{0}".format (time.strftime("%Y-%m-%d-%H%M%S"))
|
restore_home_backupdir = "home-pre-restore-{0}".format(
|
||||||
|
time.strftime("%Y-%m-%d-%H%M%S"))
|
||||||
|
|
||||||
if callable(print_callback):
|
if callable(print_callback):
|
||||||
print_callback("-> Restoring home of user '{0}'...".format(local_user))
|
print_callback(
|
||||||
print_callback("--> Existing files/dirs backed up in '{0}' dir".format(restore_home_backupdir))
|
"-> Restoring home of user '{0}'...".format(local_user))
|
||||||
|
print_callback(
|
||||||
|
"--> Existing files/dirs backed up in '{0}' dir".format(
|
||||||
|
restore_home_backupdir))
|
||||||
os.mkdir(home_dir + '/' + restore_home_backupdir)
|
os.mkdir(home_dir + '/' + restore_home_backupdir)
|
||||||
for f in os.listdir(backup_dom0_home_dir):
|
for f in os.listdir(backup_dom0_home_dir):
|
||||||
home_file = home_dir + '/' + f
|
home_file = home_dir + '/' + f
|
||||||
if os.path.exists(home_file):
|
if os.path.exists(home_file):
|
||||||
os.rename(home_file, home_dir + '/' + restore_home_backupdir + '/' + f)
|
os.rename(home_file,
|
||||||
|
home_dir + '/' + restore_home_backupdir + '/' + f)
|
||||||
if format_version == 1:
|
if format_version == 1:
|
||||||
retcode = subprocess.call (["cp", "-nrp", backup_dom0_home_dir + '/' + f, home_file])
|
retcode = subprocess.call(
|
||||||
|
["cp", "-nrp", backup_dom0_home_dir + '/' + f, home_file])
|
||||||
elif format_version >= 2:
|
elif format_version >= 2:
|
||||||
shutil.move(backup_dom0_home_dir + '/' + f, home_file)
|
shutil.move(backup_dom0_home_dir + '/' + f, home_file)
|
||||||
retcode = subprocess.call(['sudo', 'chown', '-R', local_user, home_dir])
|
retcode = subprocess.call(['sudo', 'chown', '-R', local_user, home_dir])
|
||||||
|
Loading…
Reference in New Issue
Block a user