Skip to content

Commit ccb5668

Browse files
committed
Enable more common compression filters for restoring
Since some users want pigz, zstd and some other compressions which might not be installed by default, enable them if they are installed. Related: QubesOS/qubes-issues#8291 Related: QubesOS/qubes-issues#8211
1 parent 812389b commit ccb5668

File tree

3 files changed

+92
-1
lines changed

3 files changed

+92
-1
lines changed

.gitlab-ci.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ checks:tests:
2020
- ci/codecov-wrapper
2121
before_script:
2222
- sudo dnf install -y openssl python3-rpm sequoia-sqv python3-xlib libnotify
23+
- sudo dnf install -y zstd
2324
- sudo dnf install -y --enablerepo=qubes-host-r4.3-current-testing scrypt
2425
- pip3 install --quiet -r ci/requirements.txt
2526
- git config --global --add safe.directory "$PWD"

qubesadmin/backup/restore.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666
DEFAULT_HMAC_ALGORITHM = 'scrypt'
6767
DEFAULT_COMPRESSION_FILTER = 'gzip'
6868
KNOWN_COMPRESSION_FILTERS = ('gzip', 'bzip2', 'xz')
69+
OPTIONAL_COMPRESSION_FILTERS = ('lzma', 'pigz', 'zstd', 'zstdmt')
6970
# lazy loaded
7071
KNOWN_CRYPTO_ALGORITHMS = []
7172
# lazy loaded
@@ -101,6 +102,13 @@ def init_supported_hmac_and_crypto():
101102
if not KNOWN_CRYPTO_ALGORITHMS:
102103
KNOWN_CRYPTO_ALGORITHMS.extend(get_supported_crypto_algo())
103104

105+
def validate_compression_filter(compressor: str):
106+
'''Check if the compression algorithm is available on the system'''
107+
if compressor in KNOWN_COMPRESSION_FILTERS:
108+
return True
109+
if compressor in OPTIONAL_COMPRESSION_FILTERS and shutil.which(compressor):
110+
return True
111+
return False
104112

105113
class BackupHeader(object):
106114
'''Structure describing backup-header file included as the first file in
@@ -118,7 +126,7 @@ class BackupHeader(object):
118126
'compression-filter': Header(
119127
field='compression_filter',
120128
t=str,
121-
validator=lambda x: x in KNOWN_COMPRESSION_FILTERS),
129+
validator=validate_compression_filter),
122130
'crypto-algorithm': Header(
123131
field='crypto_algorithm',
124132
t=str,
@@ -203,6 +211,11 @@ def load(self, untrusted_header_text):
203211
raise QubesException("Unrecognized header type")
204212
if not header.validator(value):
205213
if key == 'compression-filter':
214+
if value in OPTIONAL_COMPRESSION_FILTERS:
215+
raise QubesException(
216+
f"Optional '{value}' compression filter is not "
217+
"installed. Install it and retry."
218+
)
206219
raise QubesException(
207220
"Unusual compression filter '{f}' found. Use "
208221
"--compression-filter={f} to use it anyway.".format(

qubesadmin/tests/backup/backupcompatibility.py

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2046,6 +2046,83 @@ def test_230_r4_uncommon_cmpression_forced(self):
20462046

20472047
self.assertDom0Restored(dummy_timestamp)
20482048

2049+
@unittest.skipUnless(shutil.which('scrypt'),
2050+
"scrypt not installed")
2051+
def test_230_r4_optional_compression(self):
2052+
self.create_v4_backup("zstdmt")
2053+
2054+
self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = (
2055+
b'0\0dom0 class=AdminVM state=Running\n'
2056+
b'fedora-25 class=TemplateVM state=Halted\n'
2057+
b'testvm class=AppVM state=Running\n'
2058+
b'sys-net class=AppVM state=Running\n'
2059+
)
2060+
self.app.expected_calls[
2061+
('dom0', 'admin.property.Get', 'default_template', None)] = \
2062+
b'0\0default=no type=vm fedora-25'
2063+
self.app.expected_calls[
2064+
('sys-net', 'admin.vm.property.Get', 'provides_network', None)] = \
2065+
b'0\0default=no type=bool True'
2066+
self.setup_expected_calls(parsed_qubes_xml_v4, templates_map={
2067+
'debian-8': 'fedora-25'
2068+
})
2069+
firewall_data = (
2070+
'action=accept specialtarget=dns\n'
2071+
'action=accept proto=icmp\n'
2072+
'action=accept proto=tcp dstports=22-22\n'
2073+
'action=accept proto=tcp dsthost=www.qubes-os.org '
2074+
'dstports=443-443\n'
2075+
'action=accept proto=tcp dst4=192.168.0.0/24\n'
2076+
'action=drop\n'
2077+
)
2078+
self.app.expected_calls[
2079+
('test-work', 'admin.vm.firewall.Set', None,
2080+
firewall_data.encode())] = b'0\0'
2081+
2082+
qubesd_calls_queue = multiprocessing.Queue()
2083+
2084+
dummy_timestamp = time.strftime("test-%Y-%m-%d-%H%M%S")
2085+
patches = [
2086+
mock.patch('qubesadmin.storage.Volume',
2087+
staticmethod(
2088+
functools.partial(MockVolume, qubesd_calls_queue, 0)),
2089+
),
2090+
mock.patch(
2091+
'qubesadmin.backup.restore.BackupRestore._handle_appmenus_list',
2092+
staticmethod(
2093+
functools.partial(self.mock_appmenus, qubesd_calls_queue)),
2094+
),
2095+
mock.patch(
2096+
'qubesadmin.firewall.Firewall',
2097+
staticmethod(
2098+
functools.partial(MockFirewall, qubesd_calls_queue)),
2099+
),
2100+
mock.patch(
2101+
'time.strftime',
2102+
return_value=dummy_timestamp)
2103+
]
2104+
for patch in patches:
2105+
patch.start()
2106+
try:
2107+
self.restore_backup(self.fullpath("backup.bin"), options={
2108+
'use-default-template': True,
2109+
'use-default-netvm': True,
2110+
})
2111+
finally:
2112+
for patch in patches:
2113+
patch.stop()
2114+
2115+
# retrieve calls from other multiprocess.Process instances
2116+
while not qubesd_calls_queue.empty():
2117+
call_args = qubesd_calls_queue.get()
2118+
with contextlib.suppress(qubesadmin.exc.QubesException):
2119+
self.app.qubesd_call(*call_args)
2120+
qubesd_calls_queue.close()
2121+
2122+
self.assertAllCalled()
2123+
2124+
self.assertDom0Restored(dummy_timestamp)
2125+
20492126
@unittest.skipUnless(shutil.which('scrypt'),
20502127
"scrypt not installed")
20512128
def test_300_r4_no_space(self):

0 commit comments

Comments
 (0)