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.

Signed-off-by: Jeff Chase <jnch...@google.com>
---
 lib/portage/tests/util/test_install_mask.py | 26 ++++++++++++++++++++-
 lib/portage/util/install_mask.py            | 18 ++++++++------
 2 files changed, 36 insertions(+), 8 deletions(-)

diff --git a/lib/portage/tests/util/test_install_mask.py 
b/lib/portage/tests/util/test_install_mask.py
index 6a29db79a..665a8edac 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
 # 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):
@@ -163,3 +166,24 @@ class InstallMaskTestCase(TestCase):
                                self.assertEqual(install_mask.match(path), 
expected,
                                        'unexpected match result for "{}" with 
path {}'.\
                                        format(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 9442128bd..6fd393483 100644
--- a/lib/portage/util/install_mask.py
+++ b/lib/portage/util/install_mask.py
@@ -165,22 +165,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)
 
-- 
2.31.1.527.g47e6f16901-goog


Reply via email to