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(

Reply via email to