Merge branch 'new-backups'

Conflicts:
	core-modules/000QubesVm.py
This commit is contained in:
Marek Marczykowski-Górecki 2013-11-29 04:00:58 +01:00
commit 27f6f0e64e
8 changed files with 1490 additions and 643 deletions

View File

@ -123,6 +123,9 @@ class QubesVm(object):
"default_user": { "default": "user" },
"qrexec_timeout": { "default": 60 },
"autostart": { "default": False, "attr": "_autostart" },
"backup_content" : { 'default': False },
"backup_size" : { 'default': 0, "eval": "int(value)" },
"backup_path" : { 'default': "" },
##### Internal attributes - will be overriden in __init__ regardless of args
"config_file_template": { "eval": 'system_path["config_template_pv"]' },
"icon_path": { "eval": 'os.path.join(self.dir_path, "icon.png") if self.dir_path is not None else None' },
@ -140,7 +143,9 @@ class QubesVm(object):
'uses_default_kernel', 'kernel', 'uses_default_kernelopts',\
'kernelopts', 'services', 'installed_by_rpm',\
'uses_default_netvm', 'include_in_backups', 'debug',\
'default_user', 'qrexec_timeout', 'autostart' ]:
'default_user', 'qrexec_timeout', 'autostart',
'default_user', 'qrexec_timeout',
'backup_content', 'backup_size', 'backup_path' ]:
attrs[prop]['save'] = 'str(self.%s)' % prop
# Simple paths
for prop in ['conf_file', 'root_img', 'volatile_img', 'private_img']:

View File

@ -25,9 +25,13 @@ from qubes.qubes import QubesNetVm,register_qubes_vm_class,xl_ctx,xc
from qubes.qubes import defaults
from qubes.qubes import QubesException,dry_run
class QubesDom0NetVm(QubesNetVm):
class QubesAdminVm(QubesNetVm):
# In which order load this VM type from qubes.xml
load_order = 10
def __init__(self, **kwargs):
super(QubesDom0NetVm, self).__init__(qid=0, name="dom0", netid=0,
super(QubesAdminVm, self).__init__(qid=0, name="dom0", netid=0,
dir_path=None,
private_img = None,
template = None,
@ -35,6 +39,10 @@ class QubesDom0NetVm(QubesNetVm):
**kwargs)
self.xid = 0
@property
def type(self):
return "AdminVM"
def is_running(self):
return True
@ -83,10 +91,7 @@ class QubesDom0NetVm(QubesNetVm):
domains = xc.domain_getinfo(0, 1)
return domains[0]
def create_xml_element(self):
return None
def verify_files(self):
return True
register_qubes_vm_class(QubesDom0NetVm)
register_qubes_vm_class(QubesAdminVm)

1369
core/backup.py Normal file

File diff suppressed because it is too large Load Diff

View File

@ -609,10 +609,6 @@ class QubesVmCollection(dict):
def load(self):
self.clear()
dom0vm = QubesDom0NetVm (collection=self)
self[dom0vm.qid] = dom0vm
self.default_netvm_qid = 0
try:
tree = lxml.etree.parse(self.qubes_store_file)
except (EnvironmentError,
@ -665,6 +661,12 @@ class QubesVmCollection(dict):
# using 123/udp port)
if self.clockvm_qid is not None:
self[self.clockvm_qid].services['ntpd'] = False
# Add dom0 if wasn't present in qubes.xml
if not 0 in self.keys():
dom0vm = QubesAdminVm (collection=self)
self[dom0vm.qid] = dom0vm
return True
def pop(self, qid):

View File

@ -28,6 +28,7 @@ import sys
import os
import subprocess
import re
import shutil
import time
import grp,pwd
from datetime import datetime
@ -754,622 +755,4 @@ class QubesWatch(object):
while True:
self.watch_single()
######## Backups #########
def get_disk_usage(file_or_dir):
if not os.path.exists(file_or_dir):
return 0
p = subprocess.Popen (["du", "-s", "--block-size=1", file_or_dir],
stdout=subprocess.PIPE)
result = p.communicate()
m = re.match(r"^(\d+)\s.*", result[0])
sz = int(m.group(1)) if m is not None else 0
return sz
def file_to_backup (file_path, sz = None):
if sz is None:
sz = os.path.getsize (system_path["qubes_store_filename"])
abs_file_path = os.path.abspath (file_path)
abs_base_dir = os.path.abspath (system_path["qubes_base_dir"]) + '/'
abs_file_dir = os.path.dirname (abs_file_path) + '/'
(nothing, dir, subdir) = abs_file_dir.partition (abs_base_dir)
assert nothing == ""
assert dir == abs_base_dir
return [ { "path" : file_path, "size": sz, "subdir": subdir} ]
def backup_prepare(base_backup_dir, vms_list = None, exclude_list = [], print_callback = print_stdout):
"""If vms = None, include all (sensible) VMs; exclude_list is always applied"""
if not os.path.exists (base_backup_dir):
raise QubesException("The target directory doesn't exist!")
files_to_backup = file_to_backup (system_path["qubes_store_filename"])
if exclude_list is None:
exclude_list = []
if vms_list is None:
qvm_collection = QubesVmCollection()
qvm_collection.lock_db_for_reading()
qvm_collection.load()
# FIXME: should be after backup completed
qvm_collection.unlock_db()
all_vms = [vm for vm in qvm_collection.values()]
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]
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 (vm.is_template() and not vm.installed_by_rpm)]
vms_list = appvms_to_backup + netvms_to_backup + template_vms_worth_backingup
vms_for_backup = vms_list
# Apply exclude list
if exclude_list:
vms_for_backup = [vm for vm in vms_list if vm.name not in exclude_list]
no_vms = len (vms_for_backup)
there_are_running_vms = False
fields_to_display = [
{ "name": "VM", "width": 16},
{ "name": "type","width": 12 },
{ "name": "size", "width": 12}
]
# Display the header
s = ""
for f in fields_to_display:
fmt="{{0:-^{0}}}-+".format(f["width"] + 1)
s += fmt.format('-')
print_callback(s)
s = ""
for f in fields_to_display:
fmt="{{0:>{0}}} |".format(f["width"] + 1)
s += fmt.format(f["name"])
print_callback(s)
s = ""
for f in fields_to_display:
fmt="{{0:-^{0}}}-+".format(f["width"] + 1)
s += fmt.format('-')
print_callback(s)
for vm in vms_for_backup:
if vm.is_template():
# handle templates later
continue
if vm.private_img is not None:
vm_sz = vm.get_disk_usage (vm.private_img)
files_to_backup += file_to_backup(vm.private_img, vm_sz )
if vm.is_appvm():
files_to_backup += file_to_backup(vm.icon_path)
if vm.updateable:
if os.path.exists(vm.dir_path + "/apps.templates"):
# template
files_to_backup += file_to_backup(vm.dir_path + "/apps.templates")
else:
# standaloneVM
files_to_backup += file_to_backup(vm.dir_path + "/apps")
if os.path.exists(vm.dir_path + "/kernels"):
files_to_backup += file_to_backup(vm.dir_path + "/kernels")
if os.path.exists (vm.firewall_conf):
files_to_backup += file_to_backup(vm.firewall_conf)
if 'appmenus_whitelist' in vm_files and \
os.path.exists(vm.dir_path + vm_files['appmenus_whitelist']):
files_to_backup += file_to_backup(vm.dir_path + vm_files['appmenus_whitelist'])
if vm.updateable:
sz = vm.get_disk_usage(vm.root_img)
files_to_backup += file_to_backup(vm.root_img, sz)
vm_sz += sz
s = ""
fmt="{{0:>{0}}} |".format(fields_to_display[0]["width"] + 1)
s += fmt.format(vm.name)
fmt="{{0:>{0}}} |".format(fields_to_display[1]["width"] + 1)
if vm.is_netvm():
s += fmt.format("NetVM" + (" + Sys" if vm.updateable else ""))
else:
s += fmt.format("AppVM" + (" + Sys" if vm.updateable else ""))
fmt="{{0:>{0}}} |".format(fields_to_display[2]["width"] + 1)
s += fmt.format(size_to_human(vm_sz))
if vm.is_running():
s += " <-- The VM is running, please shut it down before proceeding with the backup!"
there_are_running_vms = True
print_callback(s)
for vm in vms_for_backup:
if not vm.is_template():
# already handled
continue
vm_sz = vm.get_disk_utilization()
files_to_backup += file_to_backup (vm.dir_path, vm_sz)
s = ""
fmt="{{0:>{0}}} |".format(fields_to_display[0]["width"] + 1)
s += fmt.format(vm.name)
fmt="{{0:>{0}}} |".format(fields_to_display[1]["width"] + 1)
s += fmt.format("Template VM")
fmt="{{0:>{0}}} |".format(fields_to_display[2]["width"] + 1)
s += fmt.format(size_to_human(vm_sz))
if vm.is_running():
s += " <-- The VM is running, please shut it down before proceeding with the backup!"
there_are_running_vms = True
print_callback(s)
# Dom0 user home
if not 'dom0' in exclude_list:
local_user = grp.getgrnam('qubes').gr_mem[0]
home_dir = pwd.getpwnam(local_user).pw_dir
# Home dir should have only user-owned files, so fix it now to prevent
# permissions problems - some root-owned files can left after
# 'sudo bash' and similar commands
subprocess.check_call(['sudo', 'chown', '-R', local_user, home_dir])
home_sz = get_disk_usage(home_dir)
home_to_backup = [ { "path" : home_dir, "size": home_sz, "subdir": 'dom0-home'} ]
files_to_backup += home_to_backup
s = ""
fmt="{{0:>{0}}} |".format(fields_to_display[0]["width"] + 1)
s += fmt.format('Dom0')
fmt="{{0:>{0}}} |".format(fields_to_display[1]["width"] + 1)
s += fmt.format("User home")
fmt="{{0:>{0}}} |".format(fields_to_display[2]["width"] + 1)
s += fmt.format(size_to_human(home_sz))
print_callback(s)
total_backup_sz = 0
for file in files_to_backup:
total_backup_sz += file["size"]
s = ""
for f in fields_to_display:
fmt="{{0:-^{0}}}-+".format(f["width"] + 1)
s += fmt.format('-')
print_callback(s)
s = ""
fmt="{{0:>{0}}} |".format(fields_to_display[0]["width"] + 1)
s += fmt.format("Total size:")
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))
print_callback(s)
s = ""
for f in fields_to_display:
fmt="{{0:-^{0}}}-+".format(f["width"] + 1)
s += fmt.format('-')
print_callback(s)
stat = os.statvfs(base_backup_dir)
backup_fs_free_sz = stat.f_bsize * stat.f_bavail
print_callback("")
if (total_backup_sz > backup_fs_free_sz):
raise QubesException("Not enough space available on the backup filesystem!")
if (there_are_running_vms):
raise QubesException("Please shutdown all VMs before proceeding.")
print_callback("-> Available space: {0}".format(size_to_human(backup_fs_free_sz)))
return files_to_backup
def backup_do(base_backup_dir, files_to_backup, progress_callback = None):
total_backup_sz = 0
for file in files_to_backup:
total_backup_sz += file["size"]
backup_dir = base_backup_dir + "/qubes-{0}".format (time.strftime("%Y-%m-%d-%H%M%S"))
if os.path.exists (backup_dir):
raise QubesException("ERROR: the path {0} already exists?!".format(backup_dir))
os.mkdir (backup_dir)
if not os.path.exists (backup_dir):
raise QubesException("Strange: couldn't create backup dir: {0}?!".format(backup_dir))
bytes_backedup = 0
for file in files_to_backup:
# We prefer to use Linux's cp, because it nicely handles sparse files
progress = bytes_backedup * 100 / total_backup_sz
progress_callback(progress)
dest_dir = backup_dir + '/' + file["subdir"]
if file["subdir"] != "":
retcode = subprocess.call (["mkdir", "-p", dest_dir])
if retcode != 0:
raise QubesException("Cannot create directory: {0}?!".format(dest_dir))
retcode = subprocess.call (["cp", "-rp", file["path"], dest_dir])
if retcode != 0:
raise QubesException("Error while copying file {0} to {1}".format(file["path"], dest_dir))
bytes_backedup += file["size"]
progress = bytes_backedup * 100 / total_backup_sz
progress_callback(progress)
def backup_restore_set_defaults(options):
if 'use-default-netvm' not in options:
options['use-default-netvm'] = False
if 'use-none-netvm' not in options:
options['use-none-netvm'] = False
if 'use-default-template' not in options:
options['use-default-template'] = False
if 'dom0-home' not in options:
options['dom0-home'] = True
if 'replace-template' not in options:
options['replace-template'] = []
return options
def backup_restore_prepare(backup_dir, options = {}, host_collection = None):
# Defaults
backup_restore_set_defaults(options)
#### Private functions begin
def is_vm_included_in_backup (backup_dir, vm):
if vm.qid == 0:
# Dom0 is not included, obviously
return False
backup_vm_dir_path = vm.dir_path.replace (system_path["qubes_base_dir"], backup_dir)
if os.path.exists (backup_vm_dir_path):
return True
else:
return False
def find_template_name(template, replaces):
rx_replace = re.compile("(.*):(.*)")
for r in replaces:
m = rx_replace.match(r)
if m.group(1) == template:
return m.group(2)
return template
#### Private functions end
if not os.path.exists (backup_dir):
raise QubesException("The backup directory doesn't exist!")
backup_collection = QubesVmCollection(store_filename = backup_dir + "/qubes.xml")
backup_collection.lock_db_for_reading()
backup_collection.load()
if host_collection is None:
host_collection = QubesVmCollection()
host_collection.lock_db_for_reading()
host_collection.load()
host_collection.unlock_db()
backup_vms_list = [vm for vm in backup_collection.values()]
host_vms_list = [vm for vm in host_collection.values()]
vms_to_restore = {}
there_are_conflicting_vms = False
there_are_missing_templates = False
there_are_missing_netvms = False
dom0_username_mismatch = False
restore_home = False
# ... and the actual data
for vm in backup_vms_list:
if is_vm_included_in_backup (backup_dir, vm):
vms_to_restore[vm.name] = {}
vms_to_restore[vm.name]['vm'] = vm;
if 'exclude' in options.keys():
vms_to_restore[vm.name]['excluded'] = vm.name in options['exclude']
vms_to_restore[vm.name]['good-to-go'] = False
if host_collection.get_vm_by_name (vm.name) is not None:
vms_to_restore[vm.name]['already-exists'] = True
vms_to_restore[vm.name]['good-to-go'] = False
if vm.template is None:
vms_to_restore[vm.name]['template'] = None
else:
templatevm_name = find_template_name(vm.template.name, options['replace-template'])
vms_to_restore[vm.name]['template'] = templatevm_name
template_vm_on_host = host_collection.get_vm_by_name (templatevm_name)
# No template on the host?
if not ((template_vm_on_host is not None) and template_vm_on_host.is_template()):
# Maybe the (custom) template is in the backup?
template_vm_on_backup = backup_collection.get_vm_by_name (templatevm_name)
if template_vm_on_backup is None or not \
(is_vm_included_in_backup(backup_dir, template_vm_on_backup) and \
template_vm_on_backup.is_template()):
if options['use-default-template']:
vms_to_restore[vm.name]['orig-template'] = templatevm_name
vms_to_restore[vm.name]['template'] = host_collection.get_default_template().name
else:
vms_to_restore[vm.name]['missing-template'] = True
vms_to_restore[vm.name]['good-to-go'] = False
if vm.netvm is None:
vms_to_restore[vm.name]['netvm'] = None
else:
netvm_name = vm.netvm.name
vms_to_restore[vm.name]['netvm'] = netvm_name
# Set to None to not confuse QubesVm object from backup
# collection with host collection (further in clone_attrs). Set
# directly _netvm to suppress setter action, especially
# modifying firewall
vm._netvm = None
netvm_on_host = host_collection.get_vm_by_name (netvm_name)
# No netvm on the host?
if not ((netvm_on_host is not None) and netvm_on_host.is_netvm()):
# Maybe the (custom) netvm is in the backup?
netvm_on_backup = backup_collection.get_vm_by_name (netvm_name)
if not ((netvm_on_backup is not None) and netvm_on_backup.is_netvm() and is_vm_included_in_backup(backup_dir, netvm_on_backup)):
if options['use-default-netvm']:
vms_to_restore[vm.name]['netvm'] = host_collection.get_default_netvm().name
vm.uses_default_netvm = True
elif options['use-none-netvm']:
vms_to_restore[vm.name]['netvm'] = None
else:
vms_to_restore[vm.name]['missing-netvm'] = True
vms_to_restore[vm.name]['good-to-go'] = False
if 'good-to-go' not in vms_to_restore[vm.name].keys():
vms_to_restore[vm.name]['good-to-go'] = True
# ...and dom0 home
if options['dom0-home'] and os.path.exists(backup_dir + '/dom0-home'):
vms_to_restore['dom0'] = {}
local_user = grp.getgrnam('qubes').gr_mem[0]
dom0_homes = os.listdir(backup_dir + '/dom0-home')
if len(dom0_homes) > 1:
raise QubesException("More than one dom0 homedir in backup")
vms_to_restore['dom0']['username'] = dom0_homes[0]
if dom0_homes[0] != local_user:
vms_to_restore['dom0']['username-mismatch'] = True
if not options['ignore-dom0-username-mismatch']:
vms_to_restore['dom0']['good-to-go'] = False
if 'good-to-go' not in vms_to_restore['dom0']:
vms_to_restore['dom0']['good-to-go'] = True
return vms_to_restore
def backup_restore_print_summary(restore_info, print_callback = print_stdout):
fields = {
"qid": {"func": "vm.qid"},
"name": {"func": "('[' if vm.is_template() else '')\
+ ('{' if vm.is_netvm() else '')\
+ vm.name \
+ (']' if vm.is_template() else '')\
+ ('}' if vm.is_netvm() else '')"},
"type": {"func": "'Tpl' if vm.is_template() else \
'HVM' if vm.type == 'HVM' else \
vm.type.replace('VM','')"},
"updbl" : {"func": "'Yes' if vm.updateable else ''"},
"template": {"func": "'n/a' if vm.is_template() or vm.template is None else\
vm_info['template']"},
"netvm": {"func": "'n/a' if vm.is_netvm() and not vm.is_proxyvm() else\
('*' if vm.uses_default_netvm else '') +\
vm_info['netvm'] if vm_info['netvm'] is not None else '-'"},
"label" : {"func" : "vm.label.name"},
}
fields_to_display = ["name", "type", "template", "updbl", "netvm", "label" ]
# First calculate the maximum width of each field we want to display
total_width = 0;
for f in fields_to_display:
fields[f]["max_width"] = len(f)
for vm_info in restore_info.values():
if 'vm' in vm_info.keys():
vm = vm_info['vm']
l = len(str(eval(fields[f]["func"])))
if l > fields[f]["max_width"]:
fields[f]["max_width"] = l
total_width += fields[f]["max_width"]
print_callback("")
print_callback("The following VMs are included in the backup:")
print_callback("")
# Display the header
s = ""
for f in fields_to_display:
fmt="{{0:-^{0}}}-+".format(fields[f]["max_width"] + 1)
s += fmt.format('-')
print_callback(s)
s = ""
for f in fields_to_display:
fmt="{{0:>{0}}} |".format(fields[f]["max_width"] + 1)
s += fmt.format(f)
print_callback(s)
s = ""
for f in fields_to_display:
fmt="{{0:-^{0}}}-+".format(fields[f]["max_width"] + 1)
s += fmt.format('-')
print_callback(s)
for vm_info in restore_info.values():
# Skip non-VM here
if not 'vm' in vm_info:
continue
vm = vm_info['vm']
s = ""
for f in fields_to_display:
fmt="{{0:>{0}}} |".format(fields[f]["max_width"] + 1)
s += fmt.format(eval(fields[f]["func"]))
if 'excluded' in vm_info and vm_info['excluded']:
s += " <-- Excluded from restore"
elif 'already-exists' in vm_info:
s += " <-- A VM with the same name already exists on the host!"
elif 'missing-template' in vm_info:
s += " <-- No matching template on the host or in the backup found!"
elif 'missing-netvm' in vm_info:
s += " <-- No matching netvm on the host or in the backup found!"
elif 'orig-template' in vm_info:
s += " <-- Original template was '%s'" % (vm_info['orig-template'])
print_callback(s)
if 'dom0' in restore_info.keys():
s = ""
for f in fields_to_display:
fmt="{{0:>{0}}} |".format(fields[f]["max_width"] + 1)
if f == "name":
s += fmt.format("Dom0")
elif f == "type":
s += fmt.format("Home")
else:
s += fmt.format("")
if 'username-mismatch' in restore_info['dom0']:
s += " <-- username in backup and dom0 mismatch"
print_callback(s)
def backup_restore_do(backup_dir, restore_info, host_collection = None, print_callback = print_stdout, error_callback = print_stderr):
### Private functions begin
def restore_vm_dir (backup_dir, src_dir, dst_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
retcode = subprocess.call (["cp", "-rp", backup_src_dir, dst_dir])
if retcode != 0:
raise QubesException("*** Error while copying file {0} to {1}".format(backup_src_dir, dest_dir))
### Private functions end
lock_obtained = False
if host_collection is None:
host_collection = QubesVmCollection()
host_collection.lock_db_for_writing()
host_collection.load()
lock_obtained = True
# Add VM in right order
for (vm_class_name, vm_class) in sorted(QubesVmClasses.items(),
key=lambda _x: _x[1].load_order):
for vm_info in restore_info.values():
if not vm_info['good-to-go']:
continue
if 'vm' not in vm_info:
continue
vm = vm_info['vm']
if not vm.__class__ == vm_class:
continue
print_callback("-> Restoring {type} {0}...".format(vm.name, type=vm_class_name))
retcode = subprocess.call (["mkdir", "-p", vm.dir_path])
if retcode != 0:
error_callback("*** Cannot create directory: {0}?!".format(dest_dir))
error_callback("Skipping...")
continue
template = None
if vm.template is not None:
template_name = vm_info['template']
template = host_collection.get_vm_by_name(template_name)
new_vm = None
try:
new_vm = host_collection.add_new_vm(vm_class_name, name=vm.name,
conf_file=vm.conf_file,
dir_path=vm.dir_path,
template=template,
installed_by_rpm=False)
restore_vm_dir (backup_dir, vm.dir_path, os.path.dirname(new_vm.dir_path));
new_vm.verify_files()
except Exception as err:
error_callback("ERROR: {0}".format(err))
error_callback("*** Skipping VM: {0}".format(vm.name))
if new_vm:
host_collection.pop(new_vm.qid)
continue
try:
new_vm.clone_attrs(vm)
except Exception as err:
error_callback("ERROR: {0}".format(err))
error_callback("*** Some VM property will not be restored")
try:
new_vm.appmenus_create(verbose=True)
except Exception as err:
error_callback("ERROR during appmenu restore: {0}".format(err))
error_callback("*** VM '{0}' will not have appmenus".format(vm.name))
# Set network dependencies - only non-default netvm setting
for vm_info in restore_info.values():
if not vm_info['good-to-go']:
continue
if 'vm' not in vm_info:
continue
vm = vm_info['vm']
host_vm = host_collection.get_vm_by_name(vm.name)
if host_vm is None:
# Failed/skipped VM
continue
if not vm.uses_default_netvm:
host_vm.netvm = host_collection.get_vm_by_name (vm_info['netvm']) if vm_info['netvm'] is not None else None
host_collection.save()
if lock_obtained:
host_collection.unlock_db()
# ... and dom0 home as last step
if 'dom0' in restore_info.keys() and restore_info['dom0']['good-to-go']:
backup_info = restore_info['dom0']
local_user = grp.getgrnam('qubes').gr_mem[0]
home_dir = pwd.getpwnam(local_user).pw_dir
backup_dom0_home_dir = backup_dir + '/dom0-home/' + backup_info['username']
restore_home_backupdir = "home-pre-restore-{0}".format (time.strftime("%Y-%m-%d-%H%M%S"))
print_callback("-> 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)
for f in os.listdir(backup_dom0_home_dir):
home_file = home_dir + '/' + f
if os.path.exists(home_file):
os.rename(home_file, home_dir + '/' + restore_home_backupdir + '/' + f)
retcode = subprocess.call (["cp", "-nrp", backup_dom0_home_dir + '/' + f, home_file])
if retcode != 0:
error_callback("*** Error while copying file {0} to {1}".format(backup_dom0_home_dir + '/' + f, home_file))
retcode = subprocess.call(['sudo', 'chown', '-R', local_user, home_dir])
if retcode != 0:
error_callback("*** Error while setting home directory owner")
# vim:sw=4:et:

View File

@ -22,10 +22,12 @@
from qubes.qubes import QubesVmCollection
from qubes.qubes import QubesException
from qubes.qubesutils import backup_prepare, backup_do
from qubes.backup import backup_prepare, backup_do
from qubes.qubesutils import size_to_human
from optparse import OptionParser
import os
import sys
import getpass
def print_progress(progress):
print >> sys.stderr, "\r-> Backing up files: {0}%...".format (progress),
@ -38,6 +40,10 @@ def main():
help="Exclude the specified VM from backup (might be repeated)")
parser.add_option ("--force-root", action="store_true", dest="force_root", default=False,
help="Force to run, even with root privileges")
parser.add_option ("-d", "--dest-vm", action="store", dest="appvm",
help="The AppVM to send backups to")
parser.add_option ("-e", "--encrypt", action="store_true", dest="encrypt", default=False,
help="Encrypts the backup")
(options, args) = parser.parse_args ()
@ -66,17 +72,57 @@ def main():
files_to_backup = None
try:
files_to_backup = backup_prepare(base_backup_dir, vms_list=vms, exclude_list=options.exclude_list)
files_to_backup = backup_prepare(
vms_list=vms,
exclude_list=options.exclude_list,
hide_vm_names=options.encrypt)
except QubesException as e:
print >>sys.stderr, "ERROR: %s" % str(e)
exit(1)
total_backup_sz = reduce(lambda size, file: size+file["size"],
files_to_backup, 0)
if not options.appvm:
appvm = None
stat = os.statvfs(base_backup_dir)
backup_fs_free_sz = stat.f_bsize * stat.f_bavail
print
if (total_backup_sz > backup_fs_free_sz):
print >>sys.stderr, "ERROR: Not enough space available on the backup filesystem!"
exit(1)
print "-> Available space: {0}".format(size_to_human(backup_fs_free_sz))
else:
appvm = qvm_collection.get_vm_by_name(options.appvm)
if appvm is None:
print >>sys.stderr, "ERROR: VM {0} does not exist".format(options.appvm)
exit(1)
stat = os.statvfs('/var/tmp')
backup_fs_free_sz = stat.f_bsize * stat.f_bavail
print
if (backup_fs_free_sz < 1000000000):
print >>sys.stderr, "ERROR: Not enough space available " \
"on the local filesystem (needs 1GB for temporary files)!"
exit(1)
prompt = raw_input ("Do you want to proceed? [y/N] ")
if not (prompt == "y" or prompt == "Y"):
exit (0)
passphrase = getpass.getpass("Please enter the pass phrase that will be used to encrypt/verify the backup: ")
passphrase2 = getpass.getpass("Enter again for verification: ")
if passphrase != passphrase2:
print >>sys.stderr, "ERROR: Password mismatch"
exit(1)
try:
backup_do(base_backup_dir, files_to_backup, progress_callback=print_progress)
backup_do(base_backup_dir, files_to_backup, passphrase,
progress_callback=print_progress,
encrypt=options.encrypt,
appvm=appvm)
except QubesException as e:
print >>sys.stderr, "ERROR: %s" % str(e)
exit(1)

View File

@ -22,13 +22,15 @@
from qubes.qubes import QubesVmCollection
from qubes.qubes import QubesException
from qubes.qubesutils import backup_restore_prepare
from qubes.qubesutils import backup_restore_print_summary
from qubes.qubesutils import backup_restore_do
from qubes.backup import backup_restore_header
from qubes.backup import backup_restore_prepare
from qubes.backup import backup_restore_print_summary
from qubes.backup import backup_restore_do
from optparse import OptionParser
import os
import sys
import getpass
def main():
usage = "usage: %prog [options] <backup-dir>"
@ -58,6 +60,12 @@ def main():
parser.add_option ("--ignore-username-mismatch", action="store_true", dest="ignore_username_mismatch", default=False,
help="Ignore dom0 username mismatch while restoring homedir")
parser.add_option ("-d", "--dest-vm", action="store", dest="appvm",
help="The AppVM to send backups to")
parser.add_option ("-e", "--encrypted", action="store_true", dest="decrypt", default=False,
help="The backup is encrypted")
(options, args) = parser.parse_args ()
if (len (args) != 1):
@ -66,9 +74,9 @@ def main():
backup_dir = args[0]
if not os.path.exists (backup_dir):
print >> sys.stderr, "The backup directory doesn't exist!"
exit(1)
#if not os.path.exists (backup_dir):
# print >> sys.stderr, "The backup directory doesn't exist!"
# exit(1)
host_collection = QubesVmCollection()
host_collection.lock_db_for_writing()
@ -87,9 +95,28 @@ def main():
if options.exclude:
restore_options['exclude'] = options.exclude
appvm = None
if options.appvm is not None:
appvm = host_collection.get_vm_by_name(options.appvm)
if appvm is None:
print >>sys.stderr, "ERROR: VM {0} does not exist".format(options.appvm)
exit(1)
passphrase = getpass.getpass("Please enter the pass phrase that will be used to decrypt/verify the backup: ")
print >> sys.stderr, "Checking backup content..."
restore_tmpdir,qubes_xml = backup_restore_header(backup_dir, passphrase, encrypted=options.decrypt, appvm=appvm)
restore_info = None
try:
restore_info = backup_restore_prepare(backup_dir, options=restore_options, host_collection=host_collection)
restore_info = backup_restore_prepare(
backup_dir,
os.path.join(restore_tmpdir, qubes_xml),
passphrase,
options=restore_options,
host_collection=host_collection,
encrypt=options.decrypt,
appvm=appvm)
except QubesException as e:
print >> sys.stderr, "ERROR: %s" % str(e)
exit(1)
@ -113,8 +140,6 @@ def main():
if 'username-mismatch' in vm_info.keys():
dom0_username_mismatch = True
print
if os.geteuid() == 0:
print >> sys.stderr, "*** Running this tool as root is strongly discouraged, this will lead you in permissions problems."
if options.force_root:
@ -135,7 +160,7 @@ def main():
print "The above VMs will be copied and added to your system."
print "Exisiting VMs will not be removed."
if there_are_missing_templates:
print >> sys.stderr, "*** One or more template VM is missing on the host! ***"
if not (options.skip_broken or options.ignore_missing):
@ -179,7 +204,14 @@ def main():
if not (prompt == "y" or prompt == "Y"):
exit (0)
backup_restore_do(backup_dir, restore_info, host_collection=host_collection)
backup_restore_do(backup_dir,
restore_tmpdir,
passphrase,
restore_info,
host_collection=host_collection,
encrypted=options.decrypt,
appvm=appvm)
host_collection.unlock_db()

View File

@ -110,6 +110,8 @@ cp core/qubesutils.py $RPM_BUILD_ROOT%{python_sitearch}/qubes
cp core/qubesutils.py[co] $RPM_BUILD_ROOT%{python_sitearch}/qubes
cp core/guihelpers.py $RPM_BUILD_ROOT%{python_sitearch}/qubes
cp core/guihelpers.py[co] $RPM_BUILD_ROOT%{python_sitearch}/qubes
cp core/backup.py $RPM_BUILD_ROOT%{python_sitearch}/qubes
cp core/backup.py[co] $RPM_BUILD_ROOT%{python_sitearch}/qubes
cp core/__init__.py $RPM_BUILD_ROOT%{python_sitearch}/qubes
cp core/__init__.py[co] $RPM_BUILD_ROOT%{python_sitearch}/qubes
cp qmemman/qmemman*py $RPM_BUILD_ROOT%{python_sitearch}/qubes
@ -273,6 +275,9 @@ fi
%{python_sitearch}/qubes/guihelpers.py
%{python_sitearch}/qubes/guihelpers.pyc
%{python_sitearch}/qubes/guihelpers.pyo
%{python_sitearch}/qubes/backup.py
%{python_sitearch}/qubes/backup.pyc
%{python_sitearch}/qubes/backup.pyo
%{python_sitearch}/qubes/__init__.py
%{python_sitearch}/qubes/__init__.pyc
%{python_sitearch}/qubes/__init__.pyo