From a16d4f856d37165208ca42be37463372224acf00 Mon Sep 17 00:00:00 2001 From: Katerina Koukiou Date: Thu, 19 Dec 2024 15:02:30 +0100 Subject: [PATCH] Handle invalid UTF-8 characters in efibootmgr output Modified the code to handle invalid UTF-8 characters in the output of the efibootmgr command by using `errors="ignore"`. This prevents decode exceptions when encountering non-UTF-8 sequences in the EFI boot manager output. This should be reverted when this is fixed upstream in the efibootmgr: Possible fix: https://github.com/rhboot/efibootmgr/pull/221 Resolves: rhbz#2254801 --- pyanaconda/core/util.py | 22 ++++++++++++++----- pyanaconda/modules/storage/bootloader/efi.py | 4 +++- .../pyanaconda_tests/core/test_util.py | 15 +++++++++++++ 3 files changed, 34 insertions(+), 7 deletions(-) diff --git a/pyanaconda/core/util.py b/pyanaconda/core/util.py index 80fd05f356e..3e5128d504c 100644 --- a/pyanaconda/core/util.py +++ b/pyanaconda/core/util.py @@ -170,6 +170,7 @@ def preexec(): def _run_program(argv, root='/', stdin=None, stdout=None, env_prune=None, + ignore_decode_errors=False, log_output=True, binary_output=False, filter_stderr=False, do_preexec=True, env_add=None, user=None): """ Run an external program, log the output and return it to the caller @@ -182,6 +183,7 @@ def _run_program(argv, root='/', stdin=None, stdout=None, env_prune=None, :param stdin: The file object to read stdin from. :param stdout: Optional file object to write the output to. :param env_prune: environment variable to remove before execution + :param ignore_decode_errors: whether to ignore decode errors :param log_output: whether to log the output of command :param binary_output: whether to treat the output of command as binary data :param filter_stderr: whether to exclude the contents of stderr from the returned output @@ -201,7 +203,10 @@ def _run_program(argv, root='/', stdin=None, stdout=None, env_prune=None, (output_string, err_string) = proc.communicate() if not binary_output: - output_string = output_string.decode("utf-8") + output_string = output_string.decode( + "utf-8", + errors="strict" if not ignore_decode_errors else "ignore" + ) if output_string and output_string[-1] != "\n": output_string = output_string + "\n" @@ -261,12 +266,14 @@ def execWithRedirect(command, argv, stdin=None, stdout=None, root='/', :return: The return code of the command """ argv = [command] + argv - return _run_program(argv, stdin=stdin, stdout=stdout, root=root, env_prune=env_prune, - env_add=env_add, log_output=log_output, binary_output=binary_output, + return _run_program(argv, stdin=stdin, stdout=stdout, root=root, + env_prune=env_prune, env_add=env_add, + log_output=log_output, binary_output=binary_output, do_preexec=do_preexec)[0] -def execWithCapture(command, argv, stdin=None, root='/', env_prune=None, env_add=None, +def execWithCapture(command, argv, stdin=None, root='/', + env_prune=None, env_add=None, ignore_decode_errors=False, log_output=True, filter_stderr=False, do_preexec=True): """ Run an external program and capture standard out and err. @@ -276,6 +283,7 @@ def execWithCapture(command, argv, stdin=None, root='/', env_prune=None, env_add :param root: The directory to chroot to before running command. :param env_prune: environment variable to remove before execution :param env_add: environment variables added for the execution + :param ignore_decode_errors: whether to ignore decode errors :param log_output: Whether to log the output of command :param filter_stderr: Whether stderr should be excluded from the returned output :param do_preexec: whether to use the preexec function @@ -283,8 +291,10 @@ def execWithCapture(command, argv, stdin=None, root='/', env_prune=None, env_add """ argv = [command] + argv - return _run_program(argv, stdin=stdin, root=root, log_output=log_output, env_prune=env_prune, - env_add=env_add, filter_stderr=filter_stderr, do_preexec=do_preexec)[1] + return _run_program(argv, stdin=stdin, root=root, log_output=log_output, + env_prune=env_prune, env_add=env_add, + ignore_decode_errors=ignore_decode_errors, + filter_stderr=filter_stderr, do_preexec=do_preexec)[1] def execWithCaptureAsLiveUser(command, argv, stdin=None, root='/', log_output=True, diff --git a/pyanaconda/modules/storage/bootloader/efi.py b/pyanaconda/modules/storage/bootloader/efi.py index a42af0f04cf..ba316ab65ee 100644 --- a/pyanaconda/modules/storage/bootloader/efi.py +++ b/pyanaconda/modules/storage/bootloader/efi.py @@ -111,7 +111,9 @@ def add_efi_boot_target(self): self._add_single_efi_boot_target(parent) def remove_efi_boot_target(self): - buf = self.efibootmgr(capture=True) + # FIXME: Stop using ignore_decode_errors=True once + # https://github.com/rhboot/efibootmgr/pull/221/ is merged + buf = self.efibootmgr(capture=True, ignore_decode_errors=True) for line in buf.splitlines(): try: (slot, _product) = line.split(None, 1) diff --git a/tests/unit_tests/pyanaconda_tests/core/test_util.py b/tests/unit_tests/pyanaconda_tests/core/test_util.py index 43d128ae0e7..5ad9befa032 100644 --- a/tests/unit_tests/pyanaconda_tests/core/test_util.py +++ b/tests/unit_tests/pyanaconda_tests/core/test_util.py @@ -123,6 +123,21 @@ def test_exec_with_capture_as_live_user(self, mock_get_live_user, mock_start_pro assert mock_start_program.call_args.kwargs["env_add"] == {"TEST": "test"} assert mock_start_program.call_args.kwargs["env_prune"] == ("TEST_PRUNE",) + def test_exec_with_capture_non_utf8_handling(self): + """Test execWithCapture with non-utf8 output ignored.""" + + # Echo something that cannot be decoded as utf-8 + output = util.execWithCapture( + 'echo', ['-en', r'Hello world! \xa0\xa1\xa2'], + ignore_decode_errors=True + ) + + assert output == b'Hello world! \n' + + # If ignore_non_utf8 is False (default), the non-utf8 output should raise an Exception + with pytest.raises(UnicodeDecodeError): + util.execWithCapture('echo', ['-en', r'\xa0\xa1\xa2']) + def test_exec_readlines(self): """Test execReadlines."""