commit: 196caf376c5783c9123277711cdbd8e9711de436 Author: Florian Schmaus <flow <AT> gentoo <DOT> org> AuthorDate: Sun Jan 15 18:56:10 2023 +0000 Commit: Sam James <sam <AT> gentoo <DOT> org> CommitDate: Sun Jan 15 22:11:19 2023 +0000 URL: https://gitweb.gentoo.org/proj/portage.git/commit/?id=196caf37
lib/portage/process.py: show diag message if exec failed with E2BIG Signed-off-by: Florian Schmaus <flow <AT> gentoo.org> Closes: https://github.com/gentoo/portage/pull/978 Signed-off-by: Sam James <sam <AT> gentoo.org> NEWS | 2 ++ lib/portage/process.py | 29 ++++++++++++++++ lib/portage/tests/process/test_spawn_fail_e2big.py | 40 ++++++++++++++++++++++ 3 files changed, 71 insertions(+) diff --git a/NEWS b/NEWS index b149ecc4d..a173795b3 100644 --- a/NEWS +++ b/NEWS @@ -7,6 +7,8 @@ Features: * cleanups: Use flynt on the codebase to upgrade to Python f-strings everywhere. +* process: Show diagnostic message if exec failed with E2BIG + Bug fixes: * ebuild: the PATH variable exported to ebuilds has been changed: The PATH setting from /etc/profile.env is appended to portage-internal diff --git a/lib/portage/process.py b/lib/portage/process.py index 5ba6c7867..6760f617c 100644 --- a/lib/portage/process.py +++ b/lib/portage/process.py @@ -437,6 +437,35 @@ def spawn( except SystemExit: raise except Exception as e: + if isinstance(e, OSError) and e.errno == errno.E2BIG: + # If exec() failed with E2BIG, then this is + # potentially because the environment variables + # grew to large. The following will gather some + # stats about the environment and print a + # diagnostic message to help identifying the + # culprit. See also + # - https://bugs.gentoo.org/721088 + # - https://bugs.gentoo.org/830187 + def encoded_length(s): + return len(os.fsencode(s)) + + env_size = 0 + env_largest_name = None + env_largest_size = 0 + for env_name, env_value in env.items(): + env_name_size = encoded_length(env_name) + env_value_size = encoded_length(env_value) + # Add two for '=' and the terminating null byte. + total_size = env_name_size + env_value_size + 2 + if total_size > env_largest_size: + env_largest_name = env_name + env_largest_size = total_size + env_size += total_size + + writemsg( + f"ERROR: Executing {mycommand} failed with E2BIG. Child process environment size: {env_size} bytes. Largest environment variable: {env_largest_name} ({env_largest_size} bytes)\n" + ) + # We need to catch _any_ exception so that it doesn't # propagate out of this function and cause exiting # with anything other than os._exit() diff --git a/lib/portage/tests/process/test_spawn_fail_e2big.py b/lib/portage/tests/process/test_spawn_fail_e2big.py new file mode 100644 index 000000000..d3be51670 --- /dev/null +++ b/lib/portage/tests/process/test_spawn_fail_e2big.py @@ -0,0 +1,40 @@ +# Copyright 2023 Gentoo Authors +# Distributed under the terms of the GNU General Public License v2 + +import platform +import tempfile + +from pathlib import Path + +import portage.process + +from portage import shutil +from portage.const import BASH_BINARY +from portage.tests import TestCase + + +class SpawnE2bigTestCase(TestCase): + def testSpawnE2big(self): + if platform.system() != "Linux": + self.skipTest("not Linux") + + env = dict() + env["VERY_LARGE_ENV_VAR"] = "X" * 1024 * 256 + + tmpdir = tempfile.mkdtemp() + try: + logfile = tmpdir / Path("logfile") + echo_output = "Should never appear" + retval = portage.process.spawn( + [BASH_BINARY, "-c", "echo", echo_output], env=env, logfile=logfile + ) + + with open(logfile) as f: + logfile_content = f.read() + self.assertIn( + "Largest environment variable: VERY_LARGE_ENV_VAR (262164 bytes)", + logfile_content, + ) + self.assertEqual(retval, 1) + finally: + shutil.rmtree(tmpdir)
