backup: adjust LVM volume size when restoring its content.

Old backup metadata (old qubes.xml) does not contain info about
individual volume sizes. So, extract it from tar header (using verbose
output during restore) and resize volume accordingly.
Without this, restoring volumes larger than default would be impossible.
This commit is contained in:
Marek Marczykowski-Górecki 2016-09-29 01:55:34 +02:00
parent 0a35bd06aa
commit 20590bff57
No known key found for this signature in database
GPG Key ID: 063938BA42CFA724

View File

@ -26,6 +26,7 @@ import itertools
import logging import logging
from qubes.utils import size_to_human from qubes.utils import size_to_human
import sys import sys
import stat
import os import os
import fcntl import fcntl
import subprocess import subprocess
@ -931,9 +932,33 @@ class ExtractWorker2(Process):
debug_msg = filter(msg_re.match, new_lines) debug_msg = filter(msg_re.match, new_lines)
self.log.debug('tar2_stderr: {}'.format('\n'.join(debug_msg))) self.log.debug('tar2_stderr: {}'.format('\n'.join(debug_msg)))
new_lines = filter(lambda x: not msg_re.match(x), new_lines) new_lines = filter(lambda x: not msg_re.match(x), new_lines)
if self.adjust_output_size:
# search for first file size reported by tar, after setting
# self.adjust_output_size (so don't look at self.tar2_stderr)
# this is used only when extracting single-file archive, so don't
# bother with checking file name
file_size_re = re.compile(r"^[^ ]+ [^ ]+/[^ ]+ *([0-9]+) .*")
for line in new_lines:
match = file_size_re.match(line)
if match:
file_size = match.groups()[0]
self.resize_lvm(self.adjust_output_size, file_size)
self.adjust_output_size = None
self.tar2_stderr += new_lines self.tar2_stderr += new_lines
def resize_lvm(self, dev, size):
# FIXME: HACK
try:
subprocess.check_call(
['sudo', 'lvresize', '-f', '-L', str(size) + 'B', dev],
stdout=open(os.devnull, 'w'), stderr=subprocess.STDOUT)
except subprocess.CalledProcessError as e:
if e.returncode == 3:
# already at the right size
pass
else:
raise
def run(self): def run(self):
try: try:
self.__run__() self.__run__()
@ -966,6 +991,15 @@ class ExtractWorker2(Process):
# is extracted there # is extracted there
if dirname in self.relocate: if dirname in self.relocate:
old = old.replace(dirname, self.relocate[dirname], 1) old = old.replace(dirname, self.relocate[dirname], 1)
try:
stat_buf = os.stat(new)
if stat.S_ISBLK(stat_buf.st_mode):
# output file is block device (LVM) - adjust its
# size, otherwise it may fail
# from lack of space
self.resize_lvm(new, stat_buf.st_size)
except OSError: # ENOENT
pass
subprocess.check_call( subprocess.check_call(
['dd', 'if='+old, 'of='+new, 'conv=sparse']) ['dd', 'if='+old, 'of='+new, 'conv=sparse'])
os.unlink(old) os.unlink(old)
@ -1005,16 +1039,26 @@ class ExtractWorker2(Process):
self.handle_dir_relocations( self.handle_dir_relocations(
os.path.dirname(inner_name)) os.path.dirname(inner_name))
self.tar2_current_file = None self.tar2_current_file = None
self.adjust_output_size = None
inner_name = os.path.relpath(filename.rstrip('.000')) inner_name = os.path.relpath(filename.rstrip('.000'))
redirect_stdout = None redirect_stdout = None
if self.relocate and inner_name in self.relocate: if self.relocate and inner_name in self.relocate:
# TODO: add `dd conv=sparse` when removing tar layer # TODO: add `dd conv=sparse` when removing tar layer
tar2_cmdline = ['tar', tar2_cmdline = ['tar',
'-%sMOf' % ("t" if self.verify_only else "x"), '-%sMvvOf' % ("t" if self.verify_only else "x"),
self.restore_pipe, self.restore_pipe,
inner_name] inner_name]
output_file = self.relocate[inner_name] output_file = self.relocate[inner_name]
try:
stat_buf = os.stat(output_file)
if stat.S_ISBLK(stat_buf.st_mode):
# output file is block device (LVM) - adjust its
# size during extraction, otherwise it may fail
# from lack of space
self.adjust_output_size = output_file
except OSError: # ENOENT
pass
redirect_stdout = open(output_file, 'w') redirect_stdout = open(output_file, 'w')
elif self.relocate and \ elif self.relocate and \
os.path.dirname(inner_name) in self.relocate: os.path.dirname(inner_name) in self.relocate:
@ -1112,6 +1156,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.adjust_output_size = None
self.log.error("Error while processing '{}': {}".format( self.log.error("Error while processing '{}': {}".format(
self.tar2_current_file, details)) self.tar2_current_file, details))
@ -1136,6 +1181,7 @@ class ExtractWorker2(Process):
"\n".join(self.tar2_stderr)))) "\n".join(self.tar2_stderr))))
else: else:
# Finished extracting the tar file # Finished extracting the tar file
self.collect_tar_output()
self.tar2_process = None self.tar2_process = None
# if that was whole-directory archive, handle # if that was whole-directory archive, handle
# relocated files now # relocated files now
@ -1144,6 +1190,7 @@ class ExtractWorker2(Process):
if os.path.basename(inner_name) == '.': if os.path.basename(inner_name) == '.':
self.handle_dir_relocations( self.handle_dir_relocations(
os.path.dirname(inner_name)) os.path.dirname(inner_name))
self.adjust_output_size = None
self.log.debug("Finished extracting thread") self.log.debug("Finished extracting thread")
@ -1188,6 +1235,7 @@ class ExtractWorker3(ExtractWorker2):
"\n ".join(self.tar2_stderr))) "\n ".join(self.tar2_stderr)))
else: else:
# Finished extracting the tar file # Finished extracting the tar file
self.collect_tar_output()
self.tar2_process = None self.tar2_process = None
# if that was whole-directory archive, handle # if that was whole-directory archive, handle
# relocated files now # relocated files now
@ -1197,15 +1245,25 @@ class ExtractWorker3(ExtractWorker2):
self.handle_dir_relocations( self.handle_dir_relocations(
os.path.dirname(inner_name)) os.path.dirname(inner_name))
self.tar2_current_file = None self.tar2_current_file = None
self.adjust_output_size = None
inner_name = os.path.relpath(filename.rstrip('.000')) inner_name = os.path.relpath(filename.rstrip('.000'))
redirect_stdout = None redirect_stdout = None
if self.relocate and inner_name in self.relocate: if self.relocate and inner_name in self.relocate:
# TODO: add dd conv=sparse when removing tar layer # TODO: add dd conv=sparse when removing tar layer
tar2_cmdline = ['tar', tar2_cmdline = ['tar',
'-%sO' % ("t" if self.verify_only else "x"), '-%svvO' % ("t" if self.verify_only else "x"),
inner_name] inner_name]
output_file = self.relocate[inner_name] output_file = self.relocate[inner_name]
try:
stat_buf = os.stat(output_file)
if stat.S_ISBLK(stat_buf.st_mode):
# output file is block device (LVM) - adjust its
# size during extraction, otherwise it may fail
# from lack of space
self.adjust_output_size = output_file
except OSError: # ENOENT
pass
redirect_stdout = open(output_file, 'w') redirect_stdout = open(output_file, 'w')
elif self.relocate and \ elif self.relocate and \
os.path.dirname(inner_name) in self.relocate: os.path.dirname(inner_name) in self.relocate:
@ -1293,6 +1351,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.adjust_output_size = None
self.log.error("Error while processing '{}': {}".format( self.log.error("Error while processing '{}': {}".format(
self.tar2_current_file, details)) self.tar2_current_file, details))
@ -1320,6 +1379,7 @@ class ExtractWorker3(ExtractWorker2):
"\n".join(self.tar2_stderr)))) "\n".join(self.tar2_stderr))))
else: else:
# Finished extracting the tar file # Finished extracting the tar file
self.collect_tar_output()
self.tar2_process = None self.tar2_process = None
# if that was whole-directory archive, handle # if that was whole-directory archive, handle
# relocated files now # relocated files now
@ -1328,6 +1388,7 @@ class ExtractWorker3(ExtractWorker2):
if os.path.basename(inner_name) == '.': if os.path.basename(inner_name) == '.':
self.handle_dir_relocations( self.handle_dir_relocations(
os.path.dirname(inner_name)) os.path.dirname(inner_name))
self.adjust_output_size = None
self.log.debug("Finished extracting thread") self.log.debug("Finished extracting thread")