When case-insensitive-fs is enabled in FEATURES, the dblink.isowner
method, _owners_db class, and ConfigProtect class will be
case-insensitive. This causes the collision-protect and unmerge code
to behave correctly for a case-insensitive file system. If the file
system is case-insensitive but case-preserving, then case is preserved
in the CONTENTS data of installed packages.

X-Gentoo-Bug: 524236
X-Gentoo-Url: https://bugs.gentoo.org/show_bug.cgi?id=524236
---
 bin/dispatch-conf              |  8 +++++-
 bin/etc-update                 | 10 ++++++--
 bin/portageq                   |  7 +++---
 bin/quickpkg                   |  4 ++-
 man/make.conf.5                |  6 +++++
 pym/_emerge/depgraph.py        |  4 ++-
 pym/portage/_global_updates.py |  4 ++-
 pym/portage/const.py           |  1 +
 pym/portage/dbapi/vartree.py   | 57 +++++++++++++++++++++++++++---------------
 pym/portage/update.py          |  6 +++--
 pym/portage/util/__init__.py   |  8 +++++-
 11 files changed, 83 insertions(+), 32 deletions(-)

diff --git a/bin/dispatch-conf b/bin/dispatch-conf
index 8058d6f..b679910 100755
--- a/bin/dispatch-conf
+++ b/bin/dispatch-conf
@@ -35,6 +35,10 @@ from portage.process import find_binary, spawn
 FIND_EXTANT_CONFIGS  = "find '%s' %s -name '._cfg????_%s' ! -name '.*~' ! 
-iname '.*.bak' -print"
 DIFF_CONTENTS        = "diff -Nu '%s' '%s'"
 
+if "case-insensitive-fs" in portage.settings.features:
+    FIND_EXTANT_CONFIGS = FIND_EXTANT_CONFIGS.replace(
+        "-name '._cfg", "-iname '._cfg")
+
 # We need a secure scratch dir and python does silly verbose errors on the use 
of tempnam
 oldmask = os.umask(0o077)
 SCRATCH_DIR = None
@@ -152,7 +156,9 @@ class dispatch:
         protect_obj = portage.util.ConfigProtect(
             config_root, config_paths,
             portage.util.shlex_split(
-            portage.settings.get('CONFIG_PROTECT_MASK', '')))
+            portage.settings.get('CONFIG_PROTECT_MASK', '')),
+            case_insensitive=("case-insensitive-fs"
+            in portage.settings.features))
 
         #
         # Remove new configs identical to current
diff --git a/bin/etc-update b/bin/etc-update
index 0307688..e0f7224 100755
--- a/bin/etc-update
+++ b/bin/etc-update
@@ -113,12 +113,15 @@ scan() {
                        [[ -d ${path%/*} ]] || continue
                        local name_opt=$(get_basename_find_opt "${path##*/}")
                        path="${path%/*}"
-                       find_opts=( -maxdepth 1 -name "$name_opt" )
+                       find_opts=( -maxdepth 1 )
                else
                        # Do not traverse hidden directories such as .svn or 
.git.
                        local name_opt=$(get_basename_find_opt '*')
-                       find_opts=( -name '.*' -type d -prune -o -name 
"$name_opt" )
+                       find_opts=( -name '.*' -type d -prune -o )
                fi
+               ${case_insensitive} && \
+                       find_opts+=( -iname ) || find_opts+=( -name )
+               find_opts+=( "$name_opt" )
                find_opts+=( ! -name '.*~' ! -iname '.*.bak' -print )
 
                if [ ! -w "${path}" ] ; then
@@ -743,6 +746,7 @@ fi
 
 portage_vars=(
        CONFIG_PROTECT{,_MASK}
+       FEATURES
        PORTAGE_CONFIGROOT
        PORTAGE_INST_{G,U}ID
        PORTAGE_TMPDIR
@@ -759,6 +763,8 @@ fi
 
 export PORTAGE_TMPDIR
 SCAN_PATHS=${*:-${CONFIG_PROTECT}}
+[[ " ${FEATURES} " == *" case-insensitive-fs "* ]] && \
+       case_insensitive=true || case_insensitive=false
 
 TMP="${PORTAGE_TMPDIR}/etc-update-$$"
 trap "die terminated" SIGTERM
diff --git a/bin/portageq b/bin/portageq
index ef565d1..1618b44 100755
--- a/bin/portageq
+++ b/bin/portageq
@@ -379,8 +379,8 @@ def is_protected(argv):
        protect = portage.util.shlex_split(settings.get("CONFIG_PROTECT", ""))
        protect_mask = portage.util.shlex_split(
                settings.get("CONFIG_PROTECT_MASK", ""))
-       protect_obj = ConfigProtect(root, protect, protect_mask)
-
+       protect_obj = ConfigProtect(root, protect, protect_mask,
+               case_insensitive=("case-insensitive-fs" in settings.features))
        if protect_obj.isprotected(f):
                return 0
        return 1
@@ -414,7 +414,8 @@ def filter_protected(argv):
        protect = portage.util.shlex_split(settings.get("CONFIG_PROTECT", ""))
        protect_mask = portage.util.shlex_split(
                settings.get("CONFIG_PROTECT_MASK", ""))
-       protect_obj = ConfigProtect(root, protect, protect_mask)
+       protect_obj = ConfigProtect(root, protect, protect_mask,
+               case_insensitive=("case-insensitive-fs" in settings.features))
 
        errors = 0
 
diff --git a/bin/quickpkg b/bin/quickpkg
index cf75791..2c69a69 100755
--- a/bin/quickpkg
+++ b/bin/quickpkg
@@ -102,7 +102,9 @@ def quickpkg_atom(options, infos, arg, eout):
                        if not include_config:
                                confprot = ConfigProtect(eroot,
                                        
shlex_split(settings.get("CONFIG_PROTECT", "")),
-                                       
shlex_split(settings.get("CONFIG_PROTECT_MASK", "")))
+                                       
shlex_split(settings.get("CONFIG_PROTECT_MASK", "")),
+                                       case_insensitive=("case-insensitive-fs"
+                                       in settings.features))
                                def protect(filename):
                                        if not confprot.isprotected(filename):
                                                return False
diff --git a/man/make.conf.5 b/man/make.conf.5
index 84e894b..69d95fc 100644
--- a/man/make.conf.5
+++ b/man/make.conf.5
@@ -265,6 +265,12 @@ Build binary packages for just packages in the system set.
 Enable a special progress indicator when \fBemerge\fR(1) is calculating
 dependencies.
 .TP
+.B case\-insensitive\-fs
+Use case\-insensitive file name comparisions when merging and unmerging
+files. Most users should not enable this feature, since most filesystems
+are case\-sensitive. You should only enable this feature if you are
+using portage to install files to a case\-insensitive filesystem.
+.TP
 .B ccache
 Enable portage support for the ccache package.  If the ccache dir is not
 present in the user's environment, then portage will default to
diff --git a/pym/_emerge/depgraph.py b/pym/_emerge/depgraph.py
index 2a839d0..6f1910d 100644
--- a/pym/_emerge/depgraph.py
+++ b/pym/_emerge/depgraph.py
@@ -7813,7 +7813,9 @@ class depgraph(object):
                                settings = 
self._frozen_config.roots[root].settings
                                protect_obj[root] = 
ConfigProtect(settings["EROOT"], \
                                        
shlex_split(settings.get("CONFIG_PROTECT", "")),
-                                       
shlex_split(settings.get("CONFIG_PROTECT_MASK", "")))
+                                       
shlex_split(settings.get("CONFIG_PROTECT_MASK", "")),
+                                       case_insensitive=("case-insensitive-fs"
+                                       in settings.features))
 
                def write_changes(root, changes, file_to_write_to):
                        file_contents = None
diff --git a/pym/portage/_global_updates.py b/pym/portage/_global_updates.py
index 17dc080..81ee484 100644
--- a/pym/portage/_global_updates.py
+++ b/pym/portage/_global_updates.py
@@ -208,7 +208,9 @@ def _do_global_updates(trees, prev_mtimes, quiet=False, 
if_mtime_changed=True):
                update_config_files(root,
                        shlex_split(mysettings.get("CONFIG_PROTECT", "")),
                        shlex_split(mysettings.get("CONFIG_PROTECT_MASK", "")),
-                       repo_map, match_callback=_config_repo_match)
+                       repo_map, match_callback=_config_repo_match,
+                       case_insensitive="case-insensitive-fs"
+                       in mysettings.features)
 
                # The above global updates proceed quickly, so they
                # are considered a single mtimedb transaction.
diff --git a/pym/portage/const.py b/pym/portage/const.py
index d472075..febdb4a 100644
--- a/pym/portage/const.py
+++ b/pym/portage/const.py
@@ -125,6 +125,7 @@ SUPPORTED_FEATURES       = frozenset([
        "buildpkg",
        "buildsyspkg",
        "candy",
+       "case-insensitive-fs",
        "ccache",
        "cgroup",
        "chflags",
diff --git a/pym/portage/dbapi/vartree.py b/pym/portage/dbapi/vartree.py
index 22f41d0..faee773 100644
--- a/pym/portage/dbapi/vartree.py
+++ b/pym/portage/dbapi/vartree.py
@@ -1052,13 +1052,13 @@ class vardbapi(dbapi):
 
                def add(self, cpv):
                        eroot_len = len(self._vardb._eroot)
-                       contents = self._vardb._dblink(cpv).getcontents()
                        pkg_hash = self._hash_pkg(cpv)
-                       if not contents:
+                       db = self._vardb._dblink(cpv)
+                       if not db.getcontents():
                                # Empty path is a code used to represent empty 
contents.
                                self._add_path("", pkg_hash)
 
-                       for x in contents:
+                       for x in db._contents_keys():
                                self._add_path(x[eroot_len:], pkg_hash)
 
                        self._vardb._aux_cache["modified"].add(cpv)
@@ -1190,6 +1190,8 @@ class vardbapi(dbapi):
                        hash_pkg = owners_cache._hash_pkg
                        hash_str = owners_cache._hash_str
                        base_names = 
self._vardb._aux_cache["owners"]["base_names"]
+                       case_insensitive = "case-insensitive-fs" \
+                               in vardb.settings.features
 
                        dblink_cache = {}
 
@@ -1206,6 +1208,8 @@ class vardbapi(dbapi):
                        while path_iter:
 
                                path = path_iter.pop()
+                               if case_insensitive:
+                                       path = path.lower()
                                is_basename = os.sep != path[:1]
                                if is_basename:
                                        name = path
@@ -1236,9 +1240,11 @@ class vardbapi(dbapi):
                                                                continue
 
                                                        if is_basename:
-                                                               for p in 
dblink(cpv).getcontents():
+                                                               for p in 
dblink(cpv)._contents_keys():
                                                                        if 
os.path.basename(p) == name:
-                                                                               
owners.append((cpv, p[len(root):]))
+                                                                               
owners.append((cpv,
+                                                                               
dblink(cpv)._contents_unmap_key(
+                                                                               
p)[len(root):]))
                                                        else:
                                                                if 
dblink(cpv).isowner(path):
                                                                        
owners.append((cpv, path))
@@ -1266,8 +1272,12 @@ class vardbapi(dbapi):
                        if not path_list:
                                return
 
+                       case_insensitive = "case-insensitive-fs" \
+                               in self._vardb.settings.features
                        path_info_list = []
                        for path in path_list:
+                               if case_insensitive:
+                                       path = path.lower()
                                is_basename = os.sep != path[:1]
                                if is_basename:
                                        name = path
@@ -1285,9 +1295,11 @@ class vardbapi(dbapi):
                                dblnk = self._vardb._dblink(cpv)
                                for path, name, is_basename in path_info_list:
                                        if is_basename:
-                                               for p in dblnk.getcontents():
+                                               for p in dblnk._contents_keys():
                                                        if os.path.basename(p) 
== name:
-                                                               
search_pkg.results.append((dblnk, p[len(root):]))
+                                                               
search_pkg.results.append((dblnk,
+                                                                       
dblnk._contents_unmap_key(
+                                                                               
p)[len(root):]))
                                        else:
                                                if dblnk.isowner(path):
                                                        
search_pkg.results.append((dblnk, path))
@@ -1547,7 +1559,9 @@ class dblink(object):
                        portage.util.shlex_split(
                                self.settings.get("CONFIG_PROTECT", "")),
                        portage.util.shlex_split(
-                               self.settings.get("CONFIG_PROTECT_MASK", "")))
+                               self.settings.get("CONFIG_PROTECT_MASK", "")),
+                       case_insensitive=("case-insensitive-fs"
+                                       in self.settings.features))
 
                return self._protect_obj
 
@@ -2769,15 +2783,18 @@ class dblink(object):
                        os_filename_arg.path.join(destroot,
                        filename.lstrip(os_filename_arg.path.sep)))
 
-               pkgfiles = self.getcontents()
-               if pkgfiles and destfile in pkgfiles:
-                       return destfile
-               if pkgfiles:
+               if "case-insensitive-fs" in self.settings.features:
+                       destfile = destfile.lower()
+
+               if self._contents_contains(destfile):
+                       return self._contents_unmap_key(destfile)
+
+               if self.getcontents():
                        basename = os_filename_arg.path.basename(destfile)
                        if self._contents_basenames is None:
 
                                try:
-                                       for x in pkgfiles:
+                                       for x in self._contents_keys():
                                                _unicode_encode(x,
                                                        
encoding=_encodings['merge'],
                                                        errors='strict')
@@ -2786,7 +2803,7 @@ class dblink(object):
                                        # different value of 
sys.getfilesystemencoding(),
                                        # so fall back to utf_8 if appropriate.
                                        try:
-                                               for x in pkgfiles:
+                                               for x in self._contents_keys():
                                                        _unicode_encode(x,
                                                                
encoding=_encodings['fs'],
                                                                errors='strict')
@@ -2796,7 +2813,7 @@ class dblink(object):
                                                os = portage.os
 
                                self._contents_basenames = set(
-                                       os.path.basename(x) for x in pkgfiles)
+                                       os.path.basename(x) for x in 
self._contents_keys())
                        if basename not in self._contents_basenames:
                                # This is a shortcut that, in most cases, 
allows us to
                                # eliminate this package as an owner without 
the need
@@ -2817,7 +2834,7 @@ class dblink(object):
 
                                if os is _os_merge:
                                        try:
-                                               for x in pkgfiles:
+                                               for x in self._contents_keys():
                                                        _unicode_encode(x,
                                                                
encoding=_encodings['merge'],
                                                                errors='strict')
@@ -2826,7 +2843,7 @@ class dblink(object):
                                                # different value of 
sys.getfilesystemencoding(),
                                                # so fall back to utf_8 if 
appropriate.
                                                try:
-                                                       for x in pkgfiles:
+                                                       for x in 
self._contents_keys():
                                                                
_unicode_encode(x,
                                                                        
encoding=_encodings['fs'],
                                                                        
errors='strict')
@@ -2837,7 +2854,7 @@ class dblink(object):
 
                                self._contents_inodes = {}
                                parent_paths = set()
-                               for x in pkgfiles:
+                               for x in self._contents_keys():
                                        p_path = os.path.dirname(x)
                                        if p_path in parent_paths:
                                                continue
@@ -2862,8 +2879,8 @@ class dblink(object):
                        if p_path_list:
                                for p_path in p_path_list:
                                        x = os_filename_arg.path.join(p_path, 
basename)
-                                       if x in pkgfiles:
-                                               return x
+                                       if self._contents_contains(x):
+                                               return 
self._contents_unmap_key(x)
 
                return False
 
diff --git a/pym/portage/update.py b/pym/portage/update.py
index df4e11b..83fc3d2 100644
--- a/pym/portage/update.py
+++ b/pym/portage/update.py
@@ -282,7 +282,8 @@ def parse_updates(mycontent):
                myupd.append(mysplit)
        return myupd, errors
 
-def update_config_files(config_root, protect, protect_mask, update_iter, 
match_callback = None):
+def update_config_files(config_root, protect, protect_mask, update_iter,
+       match_callback=None, case_insensitive=False):
        """Perform global updates on /etc/portage/package.*, 
/etc/portage/profile/package.*,
        /etc/portage/profile/packages and /etc/portage/sets.
        config_root - location of files to update
@@ -406,7 +407,8 @@ def update_config_files(config_root, protect, protect_mask, 
update_iter, match_c
                                                        sys.stdout.flush()
 
        protect_obj = ConfigProtect(
-               config_root, protect, protect_mask)
+               config_root, protect, protect_mask,
+               case_insensitive=case_insensitive)
        for x in update_files:
                updating_file = os.path.join(abs_user_config, x)
                if protect_obj.isprotected(updating_file):
diff --git a/pym/portage/util/__init__.py b/pym/portage/util/__init__.py
index ad3a351..d0cca5b 100644
--- a/pym/portage/util/__init__.py
+++ b/pym/portage/util/__init__.py
@@ -1555,10 +1555,12 @@ class LazyItemsDict(UserDict):
                        return result
 
 class ConfigProtect(object):
-       def __init__(self, myroot, protect_list, mask_list):
+       def __init__(self, myroot, protect_list, mask_list,
+               case_insensitive=False):
                self.myroot = myroot
                self.protect_list = protect_list
                self.mask_list = mask_list
+               self.case_insensitive = case_insensitive
                self.updateprotect()
 
        def updateprotect(self):
@@ -1586,6 +1588,8 @@ class ConfigProtect(object):
                for x in self.mask_list:
                        ppath = normalize_path(
                                os.path.join(self.myroot, 
x.lstrip(os.path.sep)))
+                       if self.case_insensitive:
+                               ppath = ppath.lower()
                        try:
                                """Use lstat so that anything, even a broken 
symlink can be
                                protected."""
@@ -1606,6 +1610,8 @@ class ConfigProtect(object):
                masked = 0
                protected = 0
                sep = os.path.sep
+               if self.case_insensitive:
+                       obj = obj.lower()
                for ppath in self.protect:
                        if len(ppath) > masked and obj.startswith(ppath):
                                if ppath in self._dirs:
-- 
2.0.4


Reply via email to