commit: df212738bbb209356472911cda79902f0e25918e Author: Zac Medico <zmedico <AT> gentoo <DOT> org> AuthorDate: Tue Dec 5 04:23:56 2023 +0000 Commit: Zac Medico <zmedico <AT> gentoo <DOT> org> CommitDate: Wed Dec 6 16:22:56 2023 +0000 URL: https://gitweb.gentoo.org/proj/portage.git/commit/?id=df212738
BuildLogger: Close self._stdin after fork In order to ensure that we can observe EOF on the read end of the pipe, close self._stdin after fork. Since portage.locks._close_fds() already does something similar for _lock_manager instances which have a close() method, it will also work with these _file_close_wrapper objects. The portage.locks._close_fds() function calls close after fork, in the ForkProcess._bootstrap method. For more general fork coverage, we could move the _close_fds() call to the _ForkWatcher.hook method in portage/__init__.py, but I've reserved that for a later change since _close_fds() has been working fine for us where we call it now. Bug: https://bugs.gentoo.org/919072 Signed-off-by: Zac Medico <zmedico <AT> gentoo.org> lib/portage/util/_async/BuildLogger.py | 34 +++++++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/lib/portage/util/_async/BuildLogger.py b/lib/portage/util/_async/BuildLogger.py index 502b3390e5..9f8a21ab2b 100644 --- a/lib/portage/util/_async/BuildLogger.py +++ b/lib/portage/util/_async/BuildLogger.py @@ -1,4 +1,4 @@ -# Copyright 2020-2021 Gentoo Authors +# Copyright 2020-2023 Gentoo Authors # Distributed under the terms of the GNU General Public License v2 import functools @@ -6,13 +6,41 @@ import subprocess from _emerge.AsynchronousTask import AsynchronousTask +import portage from portage import os +from portage.proxy.objectproxy import ObjectProxy from portage.util import shlex_split from portage.util._async.PipeLogger import PipeLogger from portage.util._async.PopenProcess import PopenProcess from portage.util.futures import asyncio +class _file_close_wrapper(ObjectProxy): + """ + Prevent fd inheritance via fork, ensuring that we can observe + EOF on the read end of the pipe (bug 919072). + """ + + __slots__ = ("_file",) + + def __init__(self, file): + ObjectProxy.__init__(self) + object.__setattr__(self, "_file", file) + portage.locks._open_fds[file.fileno()] = self + + def _get_target(self): + return object.__getattribute__(self, "_file") + + def close(self): + file = object.__getattribute__(self, "_file") + if not file.closed: + # This must only be called if the file is open, + # which ensures that file.fileno() does not + # collide with an open lock file descriptor. + del portage.locks._open_fds[file.fileno()] + file.close() + + class BuildLogger(AsynchronousTask): """ Write to a log file, with compression support provided by PipeLogger. @@ -67,7 +95,7 @@ class BuildLogger(AsynchronousTask): os.close(log_input) os.close(filter_output) else: - self._stdin = os.fdopen(stdin, "wb", 0) + self._stdin = _file_close_wrapper(os.fdopen(stdin, "wb", 0)) os.close(filter_input) os.close(filter_output) @@ -76,7 +104,7 @@ class BuildLogger(AsynchronousTask): # that is missing or broken somehow, create a pipe that # logs directly to pipe_logger. log_input, stdin = os.pipe() - self._stdin = os.fdopen(stdin, "wb", 0) + self._stdin = _file_close_wrapper(os.fdopen(stdin, "wb", 0)) # Set background=True so that pipe_logger does not log to stdout. pipe_logger = PipeLogger(
