backup: call volume.export() just before actually extracting it

There are two reasons for this:
 - call it from a coroutine, allowing export() itself be a coroutine
 - avoid calling export() when only collecting preliminary backup
   summary

Both needs some more changes in other parts of the codebase to be useful
(see next commits).
This will be especially useful when export() will need to make some
changes (like, create a snapshot, mount something etc).

QubesOS/qubes-issues#5935
This commit is contained in:
Marek Marczykowski-Górecki 2020-07-05 22:57:49 +02:00
parent 8b760451a6
commit ebd0ca7e79
No known key found for this signature in database
GPG Key ID: 063938BA42CFA724

View File

@ -252,12 +252,27 @@ class Backup:
# pylint: disable=too-many-instance-attributes # pylint: disable=too-many-instance-attributes
class FileToBackup: class FileToBackup:
# pylint: disable=too-few-public-methods # pylint: disable=too-few-public-methods
def __init__(self, file_path, subdir=None, name=None, size=None): def __init__(self, file_path_or_func, subdir=None, name=None, size=None):
"""Store a single file to backup
:param file_path_or_func: path to the file or a function
returning one; in case of function, it can be a coroutine;
if a function is given, *name*, *subdir* and *size* needs to be
given too
:param subdir: directory in a backup archive to place file in
:param name: name of the file in the backup archive
:param size: size
"""
if callable(file_path_or_func):
assert subdir is not None \
and name is not None \
and size is not None
if size is None: if size is None:
size = qubes.storage.file.get_disk_usage(file_path) size = qubes.storage.file.get_disk_usage(file_path_or_func)
if subdir is None: if subdir is None:
abs_file_path = os.path.abspath(file_path) abs_file_path = os.path.abspath(file_path_or_func)
abs_base_dir = os.path.abspath( abs_base_dir = os.path.abspath(
qubes.config.system_path["qubes_base_dir"]) + '/' 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) + '/'
@ -269,16 +284,17 @@ class Backup:
if subdir and not subdir.endswith('/'): if subdir and not subdir.endswith('/'):
subdir += '/' subdir += '/'
#: real path to the file if name is None:
self.path = file_path name = os.path.basename(file_path_or_func)
#: real path to the file (or callable to get one)
self.path = file_path_or_func
#: size of the file #: size of the file
self.size = size self.size = size
#: directory in backup archive where file should be placed #: directory in backup archive where file should be placed
self.subdir = subdir self.subdir = subdir
#: use this name in the archive (aka rename) #: use this name in the archive (aka rename)
self.name = os.path.basename(file_path) self.name = name
if name is not None:
self.name = name
class VMToBackup: class VMToBackup:
# pylint: disable=too-few-public-methods # pylint: disable=too-few-public-methods
@ -370,7 +386,7 @@ class Backup:
if not volume.save_on_stop: if not volume.save_on_stop:
continue continue
vm_files.append(self.FileToBackup( vm_files.append(self.FileToBackup(
volume.export(), volume.export,
subdir, subdir,
name + '.img', name + '.img',
volume.usage)) volume.usage))
@ -610,18 +626,21 @@ class Backup:
# Files will be verified before untaring this. # Files will be verified before untaring this.
# Prefix the path in archive with filename["subdir"] to have it # Prefix the path in archive with filename["subdir"] to have it
# verified during untar # verified during untar
path = file_info.path
if callable(path):
path = yield from qubes.utils.coro_maybe(path())
tar_cmdline = (["tar", "-Pc", '--sparse', tar_cmdline = (["tar", "-Pc", '--sparse',
'-C', os.path.dirname(file_info.path)] + '-C', os.path.dirname(path)] +
(['--dereference'] if (['--dereference'] if
file_info.subdir != "dom0-home/" else []) + file_info.subdir != "dom0-home/" else []) +
['--xform=s:^%s:%s\\0:' % ( ['--xform=s:^%s:%s\\0:' % (
os.path.basename(file_info.path), os.path.basename(path),
file_info.subdir), file_info.subdir),
os.path.basename(file_info.path) os.path.basename(path)
]) ])
file_stat = os.stat(file_info.path) file_stat = os.stat(path)
if stat.S_ISBLK(file_stat.st_mode) or \ if stat.S_ISBLK(file_stat.st_mode) or \
file_info.name != os.path.basename(file_info.path): file_info.name != os.path.basename(path):
# tar doesn't handle content of block device, use our # tar doesn't handle content of block device, use our
# writer # writer
# also use our tar writer when renaming file # also use our tar writer when renaming file
@ -631,7 +650,7 @@ class Backup:
'--override-name=%s' % ( '--override-name=%s' % (
os.path.join(file_info.subdir, os.path.basename( os.path.join(file_info.subdir, os.path.basename(
file_info.name))), file_info.name))),
file_info.path] path]
if self.compressed: if self.compressed:
tar_cmdline.insert(-2, tar_cmdline.insert(-2,
"--use-compress-program=%s" % self.compression_filter) "--use-compress-program=%s" % self.compression_filter)