From 7f8da8aac388b3bf18f5d7d22c24c11d7cbb6505 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?= Date: Sun, 19 May 2024 01:48:35 +0200 Subject: [PATCH] Fix creating template.rpm symlink Create symlink using absolute path to the template rpm package. This is important because relative path given to qvm-template should be considered in relation to the current working directory, not template.rpm symlink location. This fixes calling `qvm-template install` using relative path to the package. Fixes QubesOS/qubes-issues#9246 --- qubesadmin/tests/tools/qvm_template.py | 34 +++++++++++--------------- qubesadmin/tools/qvm_template.py | 12 +++++---- 2 files changed, 21 insertions(+), 25 deletions(-) diff --git a/qubesadmin/tests/tools/qvm_template.py b/qubesadmin/tests/tools/qvm_template.py index 8314629e7..e6ee7f778 100644 --- a/qubesadmin/tests/tools/qvm_template.py +++ b/qubesadmin/tests/tools/qvm_template.py @@ -196,7 +196,8 @@ def test_004_verify_rpm_badname(self, mock_proc, mock_call, mock_ts): @mock.patch('os.path.exists') @mock.patch('subprocess.Popen') - def test_010_extract_rpm_success(self, mock_popen, mock_path_exists): + @mock.patch('os.symlink') + def test_010_extract_rpm_success(self, mock_symlink, mock_popen, mock_path_exists): mock_popen.return_value.__enter__.return_value = mock_popen.return_value pipe = mock.Mock() mock_popen.return_value.stdout = pipe @@ -234,20 +235,17 @@ def test_010_extract_rpm_success(self, mock_popen, mock_path_exists): ]), mock.call().__enter__(), mock.call().__exit__(None, None, None), - mock.call([ - 'ln', - '-s', - path, - dirpath + '//var/lib/qubes/vm-templates/test-vm/template.rpm' - ]), - mock.call().__enter__(), - mock.call().__exit__(None, None, None), + ]) + self.assertEqual(mock_symlink.mock_calls, [ + mock.call(path, + dirpath + '//var/lib/qubes/vm-templates/test-vm/template.rpm') ]) self.assertAllCalled() @mock.patch('os.path.exists') @mock.patch('subprocess.Popen') - def test_011_extract_rpm_fail(self, mock_popen, mock_path_exists): + @mock.patch('os.symlink') + def test_011_extract_rpm_fail(self, mock_symlink, mock_popen, mock_path_exists): for failing_call in range(1, 5): mock_popen.reset_mock() with self.subTest(failing_call=failing_call): @@ -269,6 +267,12 @@ def side_effect(_, **__): mock_popen.side_effect = side_effect mock_path_exists.return_value = True + def symlink_side_effect(_s, _d): + if failing_call >= 4: + raise OSError("Error") + return None + mock_symlink.side_effect = symlink_side_effect + with tempfile.NamedTemporaryFile() as fd, \ tempfile.TemporaryDirectory() as tmpdir: path = fd.name @@ -302,16 +306,6 @@ def side_effect(_, **__): ]), mock.call().__enter__(), mock.call().__exit__(None, None, None), - ]) + ([] if failing_call < 4 else [ - mock.call([ - 'ln', - '-s', - path, - dirpath - + '//var/lib/qubes/vm-templates/test-vm/template.rpm' - ]), - mock.call().__enter__(), - mock.call().__exit__(None, None, None), ])) self.assertAllCalled() diff --git a/qubesadmin/tools/qvm_template.py b/qubesadmin/tools/qvm_template.py index cd39c8879..bdd447648 100644 --- a/qubesadmin/tools/qvm_template.py +++ b/qubesadmin/tools/qvm_template.py @@ -768,11 +768,13 @@ def extract_rpm(name: str, path: str, target: str) -> bool: if truncate.returncode != 0: return False # and create rpm file symlink - with subprocess.Popen([ - 'ln', '-s', path, f'{target}/{PATH_PREFIX}/{name}/template.rpm' - ]) as symlink: - pass - if symlink.returncode != 0: + link_path = f'{target}/{PATH_PREFIX}/{name}/template.rpm' + try: + os.symlink(os.path.abspath(path), + f'{target}/{PATH_PREFIX}/{name}/template.rpm') + except OSError as e: + print(f"Failed to create {link_path} symlink: {e!s}", + file=sys.stderr) return False return True