commit:     5cc166489106da93c20ad3c5bb138ba8a359de2c
Author:     Jeff Chase <jnchase <AT> google <DOT> com>
AuthorDate: Tue May  4 17:29:58 2021 +0000
Commit:     Sam James <sam <AT> gentoo <DOT> org>
CommitDate: Fri Apr 15 04:26:22 2022 +0000
URL:        https://gitweb.gentoo.org/proj/portage.git/commit/?id=5cc16648

install_mask: remove masked symlinks to directories (bug 678462)

os.walk() categorizes symlinks to directories as directories so they
were being ignored by INSTALL_MASK. This change calls os.scandir()
instead which efficiently provides more control.

[sam: cherry-picked from chromiumos' third_party/portage_tool repo]
(cherry picked from commit 6473079d07351dda49a906cb89d24a9a39526bf7)
Bug: https://bugs.gentoo.org/678462
Signed-off-by: Sam James <sam <AT> gentoo.org>
Closes: https://github.com/gentoo/portage/pull/820
Signed-off-by: Sam James <sam <AT> gentoo.org>

 lib/portage/tests/util/test_install_mask.py | 33 +++++++++++++++++++++++++++--
 lib/portage/util/install_mask.py            | 18 ++++++++++------
 2 files changed, 42 insertions(+), 9 deletions(-)

diff --git a/lib/portage/tests/util/test_install_mask.py 
b/lib/portage/tests/util/test_install_mask.py
index d9558a857..0ed98b3a5 100644
--- a/lib/portage/tests/util/test_install_mask.py
+++ b/lib/portage/tests/util/test_install_mask.py
@@ -1,8 +1,11 @@
-# Copyright 2018 Gentoo Foundation
+# Copyright 2018-2022 Gentoo Foundation
 # Distributed under the terms of the GNU General Public License v2
 
+import tempfile
+from portage import os
+from portage import shutil
 from portage.tests import TestCase
-from portage.util.install_mask import InstallMask
+from portage.util.install_mask import InstallMask, install_mask_dir
 
 
 class InstallMaskTestCase(TestCase):
@@ -166,3 +169,29 @@ class InstallMaskTestCase(TestCase):
                         install_mask_str, path
                     ),
                 )
+
+    def testSymlinkDir(self):
+        """
+        Test that masked symlinks to directories are removed.
+        """
+        tmp_dir = tempfile.mkdtemp()
+
+        try:
+            base_dir = os.path.join(tmp_dir, "foo")
+            target_dir = os.path.join(tmp_dir, "foo", "bar")
+            link_name = os.path.join(tmp_dir, "foo", "baz")
+
+            os.mkdir(base_dir)
+            os.mkdir(target_dir)
+            os.symlink(target_dir, link_name)
+
+            install_mask = InstallMask("/foo/")
+            install_mask_dir(tmp_dir, install_mask)
+            self.assertFalse(
+                os.path.lexists(link_name), "failed to remove 
{}".format(link_name)
+            )
+            self.assertFalse(
+                os.path.lexists(base_dir), "failed to remove 
{}".format(base_dir)
+            )
+        finally:
+            shutil.rmtree(tmp_dir)

diff --git a/lib/portage/util/install_mask.py b/lib/portage/util/install_mask.py
index 2b65fc230..638c150ff 100644
--- a/lib/portage/util/install_mask.py
+++ b/lib/portage/util/install_mask.py
@@ -171,22 +171,26 @@ def install_mask_dir(base_dir, install_mask, 
onerror=None):
     dir_stack = []
 
     # Remove masked files.
-    for parent, dirs, files in os.walk(base_dir, onerror=onerror):
+    todo = [base_dir]
+    while todo:
+        parent = todo.pop()
         try:
             parent = _unicode_decode(parent, errors="strict")
         except UnicodeDecodeError:
             continue
+
         dir_stack.append(parent)
-        for fname in files:
+        for entry in os.scandir(parent):
             try:
-                fname = _unicode_decode(fname, errors="strict")
+                abs_path = _unicode_decode(entry.path, errors="strict")
             except UnicodeDecodeError:
                 continue
-            abs_path = os.path.join(parent, fname)
-            relative_path = abs_path[base_dir_len:]
-            if install_mask.match(relative_path):
+
+            if entry.is_dir(follow_symlinks=False):
+                todo.append(entry.path)
+            elif install_mask.match(abs_path[base_dir_len:]):
                 try:
-                    os.unlink(abs_path)
+                    os.unlink(entry.path)
                 except OSError as e:
                     onerror(e)
 

Reply via email to