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.
BUG=chromium:1069710
TEST=runtests; build_image
Change-Id: If26aff91bf8b1c58e616f6c533aa793785c561cb
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/third_party/portage_tool/+/2872928
Tested-by: Jeff Chase <jnchase@google.com>
Reviewed-by: Mike Frysinger <vapier@chromium.org>
Commit-Queue: Jeff Chase <jnchase@google.com>
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/third_party/portage_tool/+/3089602
Reviewed-by: Chris McDonald <cjmcdonald@chromium.org>
Tested-by: Chris McDonald <cjmcdonald@chromium.org>
diff --git a/lib/portage/tests/util/test_install_mask.py b/lib/portage/tests/util/test_install_mask.py
index 6a29db7..665a8ed 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 @@
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 9442128..6fd3934 100644
--- a/lib/portage/util/install_mask.py
+++ b/lib/portage/util/install_mask.py
@@ -165,22 +165,26 @@
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)