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