* [gentoo-portage-dev] [PATCH] FEATURES=case-insensitive-fs for bug #524236 @ 2014-11-13 1:22 Zac Medico 2014-11-13 10:29 ` Alexander Berntsen 0 siblings, 1 reply; 12+ messages in thread From: Zac Medico @ 2014-11-13 1:22 UTC (permalink / raw To: gentoo-portage-dev; +Cc: Zac Medico 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 --- Note that this patch has already been merged into the prefix branch. bin/dispatch-conf | 8 +++++++- bin/etc-update | 10 ++++++++-- bin/portageq | 7 ++++--- bin/quickpkg | 4 +++- man/make.conf.5 | 4 ++++ pym/_emerge/depgraph.py | 4 +++- pym/portage/_global_updates.py | 4 +++- pym/portage/const.py | 1 + pym/portage/dbapi/vartree.py | 32 +++++++++++++++++++++++++++++++- pym/portage/update.py | 6 ++++-- pym/portage/util/__init__.py | 8 +++++++- 11 files changed, 75 insertions(+), 13 deletions(-) diff --git a/bin/dispatch-conf b/bin/dispatch-conf index 8058d6f..83fbe93 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 009f116..caf88e7 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..7b7daa4 100644 --- a/man/make.conf.5 +++ b/man/make.conf.5 @@ -265,6 +265,10 @@ 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. +.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 94eaed8..6bf2be5 100644 --- a/pym/_emerge/depgraph.py +++ b/pym/_emerge/depgraph.py @@ -7805,7 +7805,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..87085de 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 8b06f4c..404b2f1 100644 --- a/pym/portage/dbapi/vartree.py +++ b/pym/portage/dbapi/vartree.py @@ -1052,6 +1052,11 @@ class vardbapi(dbapi): def add(self, cpv): eroot_len = len(self._vardb._eroot) contents = self._vardb._dblink(cpv).getcontents() + + if "case-insensitive-fs" in self._vardb.settings.features: + contents = dict((k.lower(), v) + for k, v in contents.items()) + pkg_hash = self._hash_pkg(cpv) if not contents: # Empty path is a code used to represent empty contents. @@ -1189,6 +1194,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 = {} @@ -1205,6 +1212,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,6 +1245,8 @@ class vardbapi(dbapi): if is_basename: for p in dblink(cpv).getcontents(): + if case_insensitive: + p = p.lower() if os.path.basename(p) == name: owners.append((cpv, p[len(root):])) else: @@ -1265,8 +1276,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,6 +1300,8 @@ class vardbapi(dbapi): for path, name, is_basename in path_info_list: if is_basename: for p in dblnk.getcontents(): + if case_insensitive: + p = p.lower() if os.path.basename(p) == name: search_pkg.results.append((dblnk, p[len(root):])) else: @@ -1540,7 +1557,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 @@ -2762,7 +2781,16 @@ class dblink(object): filename.lstrip(os_filename_arg.path.sep))) pkgfiles = self.getcontents() + + preserve_case = None + if "case-insensitive-fs" in self.settings.features: + destfile = destfile.lower() + preserve_case = dict((k.lower(), k) for k in pkgfiles) + pkgfiles = dict((k.lower(), v) for k, v in pkgfiles.items()) + if pkgfiles and destfile in pkgfiles: + if preserve_case is not None: + return preserve_case[destfile] return destfile if pkgfiles: basename = os_filename_arg.path.basename(destfile) @@ -2855,6 +2883,8 @@ class dblink(object): for p_path in p_path_list: x = os_filename_arg.path.join(p_path, basename) if x in pkgfiles: + if preserve_case is not None: + return preserve_case[x] return 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 ^ permalink raw reply related [flat|nested] 12+ messages in thread
* Re: [gentoo-portage-dev] [PATCH] FEATURES=case-insensitive-fs for bug #524236 2014-11-13 1:22 [gentoo-portage-dev] [PATCH] FEATURES=case-insensitive-fs for bug #524236 Zac Medico @ 2014-11-13 10:29 ` Alexander Berntsen 2014-11-13 17:58 ` Zac Medico 0 siblings, 1 reply; 12+ messages in thread From: Alexander Berntsen @ 2014-11-13 10:29 UTC (permalink / raw To: gentoo-portage-dev -----BEGIN PGP SIGNED MESSAGE----- Hash: SHA256 On 13/11/14 02:22, Zac Medico wrote: > +if "case-insensitive-fs" in portage.settings.features: > + FIND_EXTANT_CONFIGS = \ > + FIND_EXTANT_CONFIGS.replace("-name '._cfg", "-iname '._cfg") > + Splitting inside the replace will look nicer following PEP indentation (as you won't need the '\'). > +Use case\-insensitive file name comparisions when merging and unmerging > +files. > +.TP Maybe mention a) that most people can ignore this option, and b) who it's actually for. Kind of in the kernel option help style. In general I don't like this patch. It handles a bunch of cases separately by doing lower(), when I think instead it should be handled implicitly. The data should be in a structure such that it knows whether it is supposed to be upper or lowercase, and whatever's dealing with it should deal with it accordingly, rather than checking "is this case insensitive? OK lowercase it before sending it wherever". But if you think this is the best way, I'm not going to stand in the way of this patch. - -- Alexander bernalex@gentoo.org https://secure.plaimi.net/~alexander -----BEGIN PGP SIGNATURE----- Version: GnuPG v2 Comment: Using GnuPG with Thunderbird - http://www.enigmail.net/ iF4EAREIAAYFAlRkiB0ACgkQRtClrXBQc7UudAD/V1r4AR5zA54Xz+LHBVNt0bnn uQ9w+146L8WYK6pDN9gBAJxZbQREeOwxKSDjluZ1lq9XARf1rh/Eqzl58wIc8I4K =c1Em -----END PGP SIGNATURE----- ^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [gentoo-portage-dev] [PATCH] FEATURES=case-insensitive-fs for bug #524236 2014-11-13 10:29 ` Alexander Berntsen @ 2014-11-13 17:58 ` Zac Medico 2014-11-16 10:41 ` [gentoo-portage-dev] [PATCH 1/2] dblink: case insensitive support " Zac Medico 0 siblings, 1 reply; 12+ messages in thread From: Zac Medico @ 2014-11-13 17:58 UTC (permalink / raw To: gentoo-portage-dev On 11/13/2014 02:29 AM, Alexander Berntsen wrote: > On 13/11/14 02:22, Zac Medico wrote: >> +if "case-insensitive-fs" in portage.settings.features: >> + FIND_EXTANT_CONFIGS = \ >> + FIND_EXTANT_CONFIGS.replace("-name '._cfg", "-iname '._cfg") >> + > Splitting inside the replace will look nicer following PEP indentation > (as you won't need the '\'). Okay, I'll re-format it as you've suggested. >> +Use case\-insensitive file name comparisions when merging and unmerging >> +files. >> +.TP > Maybe mention a) that most people can ignore this option, and b) who > it's actually for. Kind of in the kernel option help style. Okay, I'll add something about it only being needed for case-insensitive file systems, which are usually not used. > > In general I don't like this patch. It handles a bunch of cases separately > by doing lower(), when I think instead it should be handled implicitly. > The data should be in a structure such that it knows whether it is supposed > to be upper or lowercase, and whatever's dealing with it should deal with > it accordingly, rather than checking "is this case insensitive? OK > lowercase it before sending it wherever". Aside from the ConfigProtect constructor, which has a new case_insensitive keyword parameter, all affected methods handle case transformations "implicitly", as far as API consumers are concerned. However, we could improve efficiency for some usage patterns by providing an alternative to dblink.getcontents that is oriented toward case-insensitive handling. For example, every single dblink._match_contents call currently has to transform all names to lowercase, and generate a reverse mapping from lowercase back to preserved case. The dblink._match_contents method would be more efficient if we created an alternative to dblink.getcontents that handled the transformations and cached the results. I intentionally did not implement this optimization yet, since it's probably better to do it in a separate patch, rather than complicate the current patch. > But if you think this is the best way, I'm not going to stand in the way of this patch. As discussed above, think the current approach is pretty reasonable, though I would like to optimize it with a separate patch. -- Thanks, Zac ^ permalink raw reply [flat|nested] 12+ messages in thread
* [gentoo-portage-dev] [PATCH 1/2] dblink: case insensitive support for bug #524236 2014-11-13 17:58 ` Zac Medico @ 2014-11-16 10:41 ` Zac Medico 2014-11-16 10:41 ` [gentoo-portage-dev] [PATCH 2/2] FEATURES=case-insensitive-fs " Zac Medico 2014-11-16 18:11 ` [gentoo-portage-dev] [PATCH 1/2] dblink: case insensitive support " Brian Dolbec 0 siblings, 2 replies; 12+ messages in thread From: Zac Medico @ 2014-11-16 10:41 UTC (permalink / raw To: gentoo-portage-dev; +Cc: Zac Medico This adds dblink._contents_contains, _contents_iter, and _contents_key methods that provide an interface for contents operations with "implicit" case handling. X-Gentoo-Bug: 524236 X-Gentoo-Url: https://bugs.gentoo.org/show_bug.cgi?id=524236 --- pym/portage/dbapi/vartree.py | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/pym/portage/dbapi/vartree.py b/pym/portage/dbapi/vartree.py index 8b06f4c..2d1003f 100644 --- a/pym/portage/dbapi/vartree.py +++ b/pym/portage/dbapi/vartree.py @@ -1515,6 +1515,8 @@ class dblink(object): self.contentscache = None self._contents_inodes = None self._contents_basenames = None + self._contents_case_insensitive = None + self._contents_case_reverse_map = None self._linkmap_broken = False self._device_path_map = {} self._hardlink_merge_map = {} @@ -1526,6 +1528,15 @@ class dblink(object): # compliance with RESTRICT=preserve-libs. self._preserve_libs = "preserve-libs" in mysettings.features + if "case-insensitive-fs" in self.settings.features: + self._contents_key = self._contents_key_case_insensitive + self._contents_contains = self._contents_contains_case_insensitive + self._contents_iter = self._contents_iter_case_insensitive + else: + self._contents_key = self._contents_key_case_sensitive + self._contents_contains = self._contents_contains_case_sensitive + self._contents_iter = self._contents_iter_case_sensitive + def __hash__(self): return hash(self._hash_key) @@ -1612,6 +1623,8 @@ class dblink(object): self.contentscache = None self._contents_inodes = None self._contents_basenames = None + self._contents_case_insensitive = None + self._contents_case_reverse_map = None def getcontents(self): """ @@ -1716,6 +1729,36 @@ class dblink(object): self.contentscache = pkgfiles return pkgfiles + def _contents_case_insensitive_init(self): + self._contents_case_insensitive = dict( + (k.lower(), v) for k, v in self.getcontents().items()) + self._contents_case_reverse_map = dict( + (k.lower(), k) for k in self.getcontents()) + + def _contents_iter_case_insensitive(self): + if self._contents_case_insensitive is None: + self._contents_case_insensitive_init() + return iter(self._contents_case_insensitive) + + def _contents_key_case_insensitive(self, key): + if self._contents_case_reverse_map is None: + self._contents_case_insensitive_init() + return self._contents_case_reverse_map[key] + + def _contents_contains_case_insensitive(self, key): + if self._contents_case_insensitive is None: + self._contents_case_insensitive_init() + return key.lower() in self._contents_case_insensitive + + def _contents_key_case_sensitive(self, key): + return key + + def _contents_iter_case_sensitive(self): + return iter(self.getcontents()) + + def _contents_contains_case_sensitive(self, key): + return key in self.getcontents() + def _prune_plib_registry(self, unmerge=False, needed=None, preserve_paths=None): # remove preserved libraries that don't have any consumers left -- 2.0.4 ^ permalink raw reply related [flat|nested] 12+ messages in thread
* [gentoo-portage-dev] [PATCH 2/2] FEATURES=case-insensitive-fs for bug #524236 2014-11-16 10:41 ` [gentoo-portage-dev] [PATCH 1/2] dblink: case insensitive support " Zac Medico @ 2014-11-16 10:41 ` Zac Medico 2014-11-16 18:11 ` [gentoo-portage-dev] [PATCH 1/2] dblink: case insensitive support " Brian Dolbec 1 sibling, 0 replies; 12+ messages in thread From: Zac Medico @ 2014-11-16 10:41 UTC (permalink / raw To: gentoo-portage-dev; +Cc: Zac Medico 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 | 53 +++++++++++++++++++++++++++--------------- pym/portage/update.py | 6 +++-- pym/portage/util/__init__.py | 8 ++++++- 11 files changed, 80 insertions(+), 31 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 94eaed8..6bf2be5 100644 --- a/pym/_emerge/depgraph.py +++ b/pym/_emerge/depgraph.py @@ -7805,7 +7805,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..87085de 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 2d1003f..bf7985a 100644 --- a/pym/portage/dbapi/vartree.py +++ b/pym/portage/dbapi/vartree.py @@ -1051,13 +1051,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_iter(): self._add_path(x[eroot_len:], pkg_hash) self._vardb._aux_cache["modified"].add(cpv) @@ -1189,6 +1189,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 = {} @@ -1205,6 +1207,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 @@ -1235,7 +1239,7 @@ class vardbapi(dbapi): continue if is_basename: - for p in dblink(cpv).getcontents(): + for p in dblink(cpv)._contents_iter(): if os.path.basename(p) == name: owners.append((cpv, p[len(root):])) else: @@ -1265,8 +1269,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 @@ -1284,9 +1292,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_iter(): if os.path.basename(p) == name: - search_pkg.results.append((dblnk, p[len(root):])) + search_pkg.results.append((dblnk, + dblnk._contents_case_reverse_map( + p)[len(root):])) else: if dblnk.isowner(path): search_pkg.results.append((dblnk, path)) @@ -1551,7 +1561,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 @@ -2804,15 +2816,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_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_iter(): _unicode_encode(x, encoding=_encodings['merge'], errors='strict') @@ -2821,7 +2836,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_iter(): _unicode_encode(x, encoding=_encodings['fs'], errors='strict') @@ -2831,7 +2846,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_iter()) 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 @@ -2852,7 +2867,7 @@ class dblink(object): if os is _os_merge: try: - for x in pkgfiles: + for x in self._contents_iter(): _unicode_encode(x, encoding=_encodings['merge'], errors='strict') @@ -2861,7 +2876,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_iter(): _unicode_encode(x, encoding=_encodings['fs'], errors='strict') @@ -2872,7 +2887,7 @@ class dblink(object): self._contents_inodes = {} parent_paths = set() - for x in pkgfiles: + for x in self._contents_iter(): p_path = os.path.dirname(x) if p_path in parent_paths: continue @@ -2897,8 +2912,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_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 ^ permalink raw reply related [flat|nested] 12+ messages in thread
* Re: [gentoo-portage-dev] [PATCH 1/2] dblink: case insensitive support for bug #524236 2014-11-16 10:41 ` [gentoo-portage-dev] [PATCH 1/2] dblink: case insensitive support " Zac Medico 2014-11-16 10:41 ` [gentoo-portage-dev] [PATCH 2/2] FEATURES=case-insensitive-fs " Zac Medico @ 2014-11-16 18:11 ` Brian Dolbec 2014-11-17 1:29 ` [gentoo-portage-dev] [PATCH v2 " Zac Medico 1 sibling, 1 reply; 12+ messages in thread From: Brian Dolbec @ 2014-11-16 18:11 UTC (permalink / raw To: gentoo-portage-dev On Sun, 16 Nov 2014 02:41:54 -0800 Zac Medico <zmedico@gentoo.org> wrote: > This adds dblink._contents_contains, _contents_iter, and > _contents_key methods that provide an interface for contents > operations with "implicit" case handling. > > X-Gentoo-Bug: 524236 > X-Gentoo-Url: https://bugs.gentoo.org/show_bug.cgi?id=524236 > --- > pym/portage/dbapi/vartree.py | 43 > +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 > insertions(+) > > diff --git a/pym/portage/dbapi/vartree.py > b/pym/portage/dbapi/vartree.py index 8b06f4c..2d1003f 100644 > --- a/pym/portage/dbapi/vartree.py > +++ b/pym/portage/dbapi/vartree.py > @@ -1515,6 +1515,8 @@ class dblink(object): > self.contentscache = None > self._contents_inodes = None > self._contents_basenames = None > + self._contents_case_insensitive = None > + self._contents_case_reverse_map = None I like that you've prefixed the new variables with _contents_. I also see there already are a number of _contents_ prefixed variables and now newly added functions below. > self._linkmap_broken = False > self._device_path_map = {} > self._hardlink_merge_map = {} > @@ -1526,6 +1528,15 @@ class dblink(object): > # compliance with RESTRICT=preserve-libs. > self._preserve_libs = "preserve-libs" in > mysettings.features > + if "case-insensitive-fs" in self.settings.features: > + self._contents_key = > self._contents_key_case_insensitive > + self._contents_contains = > self._contents_contains_case_insensitive > + self._contents_iter = > self._contents_iter_case_insensitive > + else: > + self._contents_key = > self._contents_key_case_sensitive > + self._contents_contains = > self._contents_contains_case_sensitive > + self._contents_iter = > self._contents_iter_case_sensitive + > def __hash__(self): > return hash(self._hash_key) > > @@ -1612,6 +1623,8 @@ class dblink(object): > self.contentscache = None > self._contents_inodes = None > self._contents_basenames = None > + self._contents_case_insensitive = None > + self._contents_case_reverse_map = None > > def getcontents(self): > """ > @@ -1716,6 +1729,36 @@ class dblink(object): > self.contentscache = pkgfiles > return pkgfiles > > + def _contents_case_insensitive_init(self): > + self._contents_case_insensitive = dict( > + (k.lower(), v) for k, v in > self.getcontents().items()) > + self._contents_case_reverse_map = dict( > + (k.lower(), k) for k in self.getcontents()) > + > + def _contents_iter_case_insensitive(self): > + if self._contents_case_insensitive is None: > + self._contents_case_insensitive_init() > + return iter(self._contents_case_insensitive) > + > + def _contents_key_case_insensitive(self, key): > + if self._contents_case_reverse_map is None: > + self._contents_case_insensitive_init() > + return self._contents_case_reverse_map[key] > + > + def _contents_contains_case_insensitive(self, key): > + if self._contents_case_insensitive is None: > + self._contents_case_insensitive_init() > + return key.lower() in self._contents_case_insensitive > + > + def _contents_key_case_sensitive(self, key): > + return key > + > + def _contents_iter_case_sensitive(self): > + return iter(self.getcontents()) > + > + def _contents_contains_case_sensitive(self, key): > + return key in self.getcontents() > + > def _prune_plib_registry(self, unmerge=False, > needed=None, preserve_paths=None): > # remove preserved libraries that don't have any > consumers left These truly have little interaction with the rest of the dblink class. I would like to see them moved to an independent class. With that move the names could be shortened without the _contents prefix since they would be Contents class referenced. It also looks like there should be additional code from the dblink class moved into this new class. Primarily the getcontents(), but that is a much larger refactor. That would also require the cache, re's and probably some others. Then all the references to these functions in the vardbapi to the class instance instead. On that note, I propose we put this new code in a new class (avoiding adding to the bloat), pass in the getcontents() pointer along with some others needed. In later commits do the major re-factor moving more contents specific handling code into the new class. Bare in mind I have not looked that extensively in dblink and vardbapi, but it does look doable. Are you up for the challenge? ;) I think it would certainly make the code more maintainable, less daunting and more clearly defined. We could even split vartree.py into some smaller files or sub-pkg. I dislike multi-thousand lines of code files and classes. It makes it much more difficult to move around and keep track of interactions between the code (complexity) and to learn everything it does. -- Brian Dolbec <dolsen> ^ permalink raw reply [flat|nested] 12+ messages in thread
* [gentoo-portage-dev] [PATCH v2 1/2] dblink: case insensitive support for bug #524236 2014-11-16 18:11 ` [gentoo-portage-dev] [PATCH 1/2] dblink: case insensitive support " Brian Dolbec @ 2014-11-17 1:29 ` Zac Medico 2014-11-17 1:29 ` [gentoo-portage-dev] [PATCH v2 2/2] FEATURES=case-insensitive-fs " Zac Medico 2014-11-17 4:58 ` [gentoo-portage-dev] [PATCH v2 1/2] dblink: case insensitive support " Brian Dolbec 0 siblings, 2 replies; 12+ messages in thread From: Zac Medico @ 2014-11-17 1:29 UTC (permalink / raw To: gentoo-portage-dev; +Cc: Zac Medico This adds dblink._contents_contains, _contents_keys, and _contents_map_key methods that provide an interface for contents operations with "implicit" case handling. The new methods are implemented in a separate ContentsCaseSensitivityManager class, in order to avoid adding more bloat to vartree.py. X-Gentoo-Bug: 524236 X-Gentoo-Url: https://bugs.gentoo.org/show_bug.cgi?id=524236 --- .../dbapi/_ContentsCaseSensitivityManager.py | 54 ++++++++++++++++++++++ pym/portage/dbapi/vartree.py | 8 ++++ 2 files changed, 62 insertions(+) create mode 100644 pym/portage/dbapi/_ContentsCaseSensitivityManager.py diff --git a/pym/portage/dbapi/_ContentsCaseSensitivityManager.py b/pym/portage/dbapi/_ContentsCaseSensitivityManager.py new file mode 100644 index 0000000..6b8301d --- /dev/null +++ b/pym/portage/dbapi/_ContentsCaseSensitivityManager.py @@ -0,0 +1,54 @@ +# Copyright 2014 Gentoo Foundation +# Distributed under the terms of the GNU General Public License v2 + +class ContentsCaseSensitivityManager(object): + + def __init__(self, db): + + self.getcontents = db.getcontents + + if "case-insensitive-fs" in db.settings.features: + self.unmap_key = self._unmap_key_case_insensitive + self.contains = self._contains_case_insensitive + self.keys = self._keys_case_insensitive + else: + self.unmap_key = self._unmap_key_case_sensitive + self.contains = self._contains_case_sensitive + self.keys = self._keys_case_sensitive + + self._contents_insensitive = None + self._reverse_key_map = None + + def clear_cache(self): + self._contents_insensitive = None + self._reverse_key_map = None + + def _case_insensitive_init(self): + self._contents_insensitive = dict( + (k.lower(), v) for k, v in self.getcontents().items()) + self._reverse_key_map = dict( + (k.lower(), k) for k in self.getcontents()) + + def _keys_case_insensitive(self): + if self._contents_insensitive is None: + self._case_insensitive_init() + return iter(self._contents_insensitive) + + def _contains_case_insensitive(self, key): + if self._contents_insensitive is None: + self._case_insensitive_init() + return key.lower() in self._contents_insensitive + + def _unmap_key_case_insensitive(self, key): + if self._reverse_key_map is None: + self._case_insensitive_init() + return self._reverse_key_map[key] + + def _keys_case_sensitive(self): + return iter(self.getcontents()) + + def _contains_case_sensitive(self, key): + return key in self.getcontents() + + def _unmap_key_case_sensitive(self, key): + return key diff --git a/pym/portage/dbapi/vartree.py b/pym/portage/dbapi/vartree.py index 8b06f4c..22f41d0 100644 --- a/pym/portage/dbapi/vartree.py +++ b/pym/portage/dbapi/vartree.py @@ -69,6 +69,7 @@ from _emerge.EbuildPhase import EbuildPhase from _emerge.emergelog import emergelog from _emerge.MiscFunctionsProcess import MiscFunctionsProcess from _emerge.SpawnProcess import SpawnProcess +from ._ContentsCaseSensitivityManager import ContentsCaseSensitivityManager import errno import fnmatch @@ -1526,6 +1527,12 @@ class dblink(object): # compliance with RESTRICT=preserve-libs. self._preserve_libs = "preserve-libs" in mysettings.features + manager = ContentsCaseSensitivityManager(self) + self._contents_case_sensitivity_manager = manager + self._contents_unmap_key = manager.unmap_key + self._contents_contains = manager.contains + self._contents_keys = manager.keys + def __hash__(self): return hash(self._hash_key) @@ -1612,6 +1619,7 @@ class dblink(object): self.contentscache = None self._contents_inodes = None self._contents_basenames = None + self._contents_case_sensitivity_manager.clear_cache() def getcontents(self): """ -- 2.0.4 ^ permalink raw reply related [flat|nested] 12+ messages in thread
* [gentoo-portage-dev] [PATCH v2 2/2] FEATURES=case-insensitive-fs for bug #524236 2014-11-17 1:29 ` [gentoo-portage-dev] [PATCH v2 " Zac Medico @ 2014-11-17 1:29 ` Zac Medico 2014-11-17 4:58 ` [gentoo-portage-dev] [PATCH v2 1/2] dblink: case insensitive support " Brian Dolbec 1 sibling, 0 replies; 12+ messages in thread From: Zac Medico @ 2014-11-17 1:29 UTC (permalink / raw To: gentoo-portage-dev; +Cc: Zac Medico 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 ^ permalink raw reply related [flat|nested] 12+ messages in thread
* Re: [gentoo-portage-dev] [PATCH v2 1/2] dblink: case insensitive support for bug #524236 2014-11-17 1:29 ` [gentoo-portage-dev] [PATCH v2 " Zac Medico 2014-11-17 1:29 ` [gentoo-portage-dev] [PATCH v2 2/2] FEATURES=case-insensitive-fs " Zac Medico @ 2014-11-17 4:58 ` Brian Dolbec 2014-11-17 20:29 ` [gentoo-portage-dev] [PATCH v3 " Zac Medico 1 sibling, 1 reply; 12+ messages in thread From: Brian Dolbec @ 2014-11-17 4:58 UTC (permalink / raw To: gentoo-portage-dev On Sun, 16 Nov 2014 17:29:26 -0800 Zac Medico <zmedico@gentoo.org> wrote: > This adds dblink._contents_contains, _contents_keys, and > _contents_map_key methods that provide an interface for contents > operations with "implicit" case handling. The new methods are > implemented in a separate ContentsCaseSensitivityManager class, > in order to avoid adding more bloat to vartree.py. > > X-Gentoo-Bug: 524236 > X-Gentoo-Url: https://bugs.gentoo.org/show_bug.cgi?id=524236 > --- > .../dbapi/_ContentsCaseSensitivityManager.py | 54 > ++++++++++++++++++++++ > pym/portage/dbapi/vartree.py | 8 ++++ 2 files > changed, 62 insertions(+) create mode 100644 > pym/portage/dbapi/_ContentsCaseSensitivityManager.py > > diff --git a/pym/portage/dbapi/_ContentsCaseSensitivityManager.py > b/pym/portage/dbapi/_ContentsCaseSensitivityManager.py new file mode > 100644 index 0000000..6b8301d > --- /dev/null > +++ b/pym/portage/dbapi/_ContentsCaseSensitivityManager.py > @@ -0,0 +1,54 @@ > +# Copyright 2014 Gentoo Foundation > +# Distributed under the terms of the GNU General Public License v2 > + > +class ContentsCaseSensitivityManager(object): > + > + def __init__(self, db): > + > + self.getcontents = db.getcontents > + > + if "case-insensitive-fs" in db.settings.features: > + self.unmap_key = > self._unmap_key_case_insensitive > + self.contains = > self._contains_case_insensitive > + self.keys = self._keys_case_insensitive > + else: > + self.unmap_key = > self._unmap_key_case_sensitive > + self.contains = self._contains_case_sensitive > + self.keys = self._keys_case_sensitive > + > + self._contents_insensitive = None > + self._reverse_key_map = None > + > + def clear_cache(self): > + self._contents_insensitive = None > + self._reverse_key_map = None > + > + def _case_insensitive_init(self): > + self._contents_insensitive = dict( > + (k.lower(), v) for k, v in > self.getcontents().items()) > + self._reverse_key_map = dict( > + (k.lower(), k) for k in self.getcontents()) > + > + def _keys_case_insensitive(self): > + if self._contents_insensitive is None: > + self._case_insensitive_init() > + return iter(self._contents_insensitive) > + > + def _contains_case_insensitive(self, key): > + if self._contents_insensitive is None: > + self._case_insensitive_init() > + return key.lower() in self._contents_insensitive > + > + def _unmap_key_case_insensitive(self, key): > + if self._reverse_key_map is None: > + self._case_insensitive_init() > + return self._reverse_key_map[key] > + > + def _keys_case_sensitive(self): > + return iter(self.getcontents()) > + > + def _contains_case_sensitive(self, key): > + return key in self.getcontents() > + > + def _unmap_key_case_sensitive(self, key): > + return key Very nice, much better this way than adding to the dblink class directly. Just one thing left to do... Docstrings please. While normally you don't NEED them for private functions, in this case they are exported using the function pointer assignments. They are named quite well to indicate their function, but document them anyway please. > diff --git a/pym/portage/dbapi/vartree.py > b/pym/portage/dbapi/vartree.py index 8b06f4c..22f41d0 100644 > --- a/pym/portage/dbapi/vartree.py > +++ b/pym/portage/dbapi/vartree.py > @@ -69,6 +69,7 @@ from _emerge.EbuildPhase import EbuildPhase > from _emerge.emergelog import emergelog > from _emerge.MiscFunctionsProcess import MiscFunctionsProcess > from _emerge.SpawnProcess import SpawnProcess > +from ._ContentsCaseSensitivityManager import > ContentsCaseSensitivityManager > import errno > import fnmatch > @@ -1526,6 +1527,12 @@ class dblink(object): > # compliance with RESTRICT=preserve-libs. > self._preserve_libs = "preserve-libs" in > mysettings.features > + manager = ContentsCaseSensitivityManager(self) > + self._contents_case_sensitivity_manager = manager > + self._contents_unmap_key = manager.unmap_key > + self._contents_contains = manager.contains > + self._contents_keys = manager.keys > + Rather than so many function pointer assignments, why not + self._contents = ContentsCaseSensitivityManager(self) Then later use it like so... + self._contents.unmap_key(...) + self._contents.contains(...) + self._contents.keys() + vardbapi calls will just get a similar one additional level of direction. <snipped from patch V2 2/2> - for p in dblink(cpv).getcontents(): + for p in dblink(cpv)._contents_keys(): to: - for p in dblink(cpv).getcontents(): + for p in dblink(cpv)._contents.keys(): It should be less confusing for future maintenance by having to track all the pointer assignments to know what it is actually pointing at. It is a lot easier to find that _contents is a ContentsCaseSensitivityManager class instance. Then all function calls directly refer to those functions or the sensitivity set exported versions of them. > def __hash__(self): > return hash(self._hash_key) > > @@ -1612,6 +1619,7 @@ class dblink(object): > self.contentscache = None > self._contents_inodes = None > self._contents_basenames = None > + self._contents_case_sensitivity_manager.clear_cache() again... + self._contents.clear_cache() > > def getcontents(self): > """ -- Brian Dolbec <dolsen> ^ permalink raw reply [flat|nested] 12+ messages in thread
* [gentoo-portage-dev] [PATCH v3 1/2] dblink: case insensitive support for bug #524236 2014-11-17 4:58 ` [gentoo-portage-dev] [PATCH v2 1/2] dblink: case insensitive support " Brian Dolbec @ 2014-11-17 20:29 ` Zac Medico 2014-11-17 20:29 ` [gentoo-portage-dev] [PATCH v3 2/2] FEATURES=case-insensitive-fs " Zac Medico 2014-11-17 22:09 ` [gentoo-portage-dev] [PATCH v3 1/2] dblink: case insensitive support " Brian Dolbec 0 siblings, 2 replies; 12+ messages in thread From: Zac Medico @ 2014-11-17 20:29 UTC (permalink / raw To: gentoo-portage-dev; +Cc: Zac Medico This adds a dblink._contents attribute with methods that provide an interface for contents operations with "implicit" case handling. The new methods are implemented in a separate ContentsCaseSensitivityManager class, in order to avoid adding more bloat to vartree.py. X-Gentoo-Bug: 524236 X-Gentoo-Url: https://bugs.gentoo.org/show_bug.cgi?id=524236 --- .../dbapi/_ContentsCaseSensitivityManager.py | 93 ++++++++++++++++++++++ pym/portage/dbapi/vartree.py | 3 + 2 files changed, 96 insertions(+) create mode 100644 pym/portage/dbapi/_ContentsCaseSensitivityManager.py diff --git a/pym/portage/dbapi/_ContentsCaseSensitivityManager.py b/pym/portage/dbapi/_ContentsCaseSensitivityManager.py new file mode 100644 index 0000000..c479ec9 --- /dev/null +++ b/pym/portage/dbapi/_ContentsCaseSensitivityManager.py @@ -0,0 +1,93 @@ +# Copyright 2014 Gentoo Foundation +# Distributed under the terms of the GNU General Public License v2 + +class ContentsCaseSensitivityManager(object): + """ + Implicitly handles case transformations that are needed for + case-insensitive support. + """ + + def __init__(self, db): + """ + @param db: A dblink instance + @type db: vartree.dblink + """ + self.getcontents = db.getcontents + + if "case-insensitive-fs" in db.settings.features: + self.unmap_key = self._unmap_key_case_insensitive + self.contains = self._contains_case_insensitive + self.keys = self._keys_case_insensitive + + self._contents_insensitive = None + self._reverse_key_map = None + + def clear_cache(self): + """ + Clear all cached contents data. + """ + self._contents_insensitive = None + self._reverse_key_map = None + + def keys(self): + """ + Iterate over all contents keys, which are transformed to + lowercase when appropriate, for use in case-insensitive + comparisons. + @rtype: iterator + @return: An iterator over all the contents keys + """ + return iter(self.getcontents()) + + def contains(self, key): + """ + Check if the given key is contained in the contents, using + case-insensitive comparison when appropriate. + @param key: A filesystem path (including ROOT and EPREFIX) + @type key: str + @rtype: bool + @return: True if the given key is contained in the contents, + False otherwise + """ + return key in self.getcontents() + + def unmap_key(self, key): + """ + Map a key (from the keys method) back to its case-preserved + form. + @param key: A filesystem path (including ROOT and EPREFIX) + @type key: str + @rtype: str + @return: The case-preserved form of key + """ + return key + + def _case_insensitive_init(self): + """ + Initialize data structures for case-insensitive support. + """ + self._contents_insensitive = dict( + (k.lower(), v) for k, v in self.getcontents().items()) + self._reverse_key_map = dict( + (k.lower(), k) for k in self.getcontents()) + + def _keys_case_insensitive(self): + if self._contents_insensitive is None: + self._case_insensitive_init() + return iter(self._contents_insensitive) + + _keys_case_insensitive.__doc__ = keys.__doc__ + + def _contains_case_insensitive(self, key): + if self._contents_insensitive is None: + self._case_insensitive_init() + return key.lower() in self._contents_insensitive + + _contains_case_insensitive.__doc__ = contains.__doc__ + + def _unmap_key_case_insensitive(self, key): + if self._reverse_key_map is None: + self._case_insensitive_init() + return self._reverse_key_map[key] + + _unmap_key_case_insensitive.__doc__ = unmap_key.__doc__ diff --git a/pym/portage/dbapi/vartree.py b/pym/portage/dbapi/vartree.py index 8b06f4c..81059b1 100644 --- a/pym/portage/dbapi/vartree.py +++ b/pym/portage/dbapi/vartree.py @@ -69,6 +69,7 @@ from _emerge.EbuildPhase import EbuildPhase from _emerge.emergelog import emergelog from _emerge.MiscFunctionsProcess import MiscFunctionsProcess from _emerge.SpawnProcess import SpawnProcess +from ._ContentsCaseSensitivityManager import ContentsCaseSensitivityManager import errno import fnmatch @@ -1525,6 +1526,7 @@ class dblink(object): # When necessary, this attribute is modified for # compliance with RESTRICT=preserve-libs. self._preserve_libs = "preserve-libs" in mysettings.features + self._contents = ContentsCaseSensitivityManager(self) def __hash__(self): return hash(self._hash_key) @@ -1612,6 +1614,7 @@ class dblink(object): self.contentscache = None self._contents_inodes = None self._contents_basenames = None + self._contents.clear_cache() def getcontents(self): """ -- 2.0.4 ^ permalink raw reply related [flat|nested] 12+ messages in thread
* [gentoo-portage-dev] [PATCH v3 2/2] FEATURES=case-insensitive-fs for bug #524236 2014-11-17 20:29 ` [gentoo-portage-dev] [PATCH v3 " Zac Medico @ 2014-11-17 20:29 ` Zac Medico 2014-11-17 22:09 ` [gentoo-portage-dev] [PATCH v3 1/2] dblink: case insensitive support " Brian Dolbec 1 sibling, 0 replies; 12+ messages in thread From: Zac Medico @ 2014-11-17 20:29 UTC (permalink / raw To: gentoo-portage-dev; +Cc: Zac Medico 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 | 71 +++++++++++++++++++++++++++--------------- pym/portage/update.py | 6 ++-- pym/portage/util/__init__.py | 8 ++++- 11 files changed, 92 insertions(+), 37 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 6a42bfd..2c4f548 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 81059b1..0fd1bd9 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,12 +1240,16 @@ 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)) + key = dblink(cpv)._match_contents(path) + if key is not False: + owners.append( + (cpv, key[len(root):])) except StopIteration: path_iter.append(path) @@ -1266,8 +1274,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,12 +1297,16 @@ 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)) + key = dblnk._match_contents(path) + if key is not False: + search_pkg.results.append( + (dblnk, key[len(root):])) search_pkg.complete = True return False @@ -1542,7 +1558,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 @@ -1620,9 +1638,9 @@ class dblink(object): """ Get the installed files of a given package (aka what that package installed) """ - contents_file = os.path.join(self.dbdir, "CONTENTS") if self.contentscache is not None: return self.contentscache + contents_file = os.path.join(self.dbdir, "CONTENTS") pkgfiles = {} try: with io.open(_unicode_encode(contents_file, @@ -2764,15 +2782,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') @@ -2781,7 +2802,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') @@ -2791,7 +2812,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 @@ -2812,7 +2833,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') @@ -2821,7 +2842,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') @@ -2832,7 +2853,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 @@ -2857,8 +2878,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 ^ permalink raw reply related [flat|nested] 12+ messages in thread
* Re: [gentoo-portage-dev] [PATCH v3 1/2] dblink: case insensitive support for bug #524236 2014-11-17 20:29 ` [gentoo-portage-dev] [PATCH v3 " Zac Medico 2014-11-17 20:29 ` [gentoo-portage-dev] [PATCH v3 2/2] FEATURES=case-insensitive-fs " Zac Medico @ 2014-11-17 22:09 ` Brian Dolbec 1 sibling, 0 replies; 12+ messages in thread From: Brian Dolbec @ 2014-11-17 22:09 UTC (permalink / raw To: gentoo-portage-dev On Mon, 17 Nov 2014 12:29:37 -0800 Zac Medico <zmedico@gentoo.org> wrote: > This adds a dblink._contents attribute with methods that provide > an interface for contents operations with "implicit" case handling. > The new methods are implemented in a separate > ContentsCaseSensitivityManager class, in order to avoid adding more > bloat to vartree.py. > > X-Gentoo-Bug: 524236 > X-Gentoo-Url: https://bugs.gentoo.org/show_bug.cgi?id=524236 > --- ... > +class ContentsCaseSensitivityManager(object): > + """ > + Implicitly handles case transformations that are needed for > + case-insensitive support. > + """ > + > + def __init__(self, db): > + """ > + @param db: A dblink instance > + @type db: vartree.dblink > + """ Thank you :) looks much better now ;) Both 1/2, 2/2 look good now. Clear to merge :) > + self.getcontents = db.getcontents > + > + if "case-insensitive-fs" in db.settings.features: > + self.unmap_key = > self._unmap_key_case_insensitive > + self.contains = > self._contains_case_insensitive > + self.keys = self._keys_case_insensitive > + > + self._contents_insensitive = None > + self._reverse_key_map = None > + > + def clear_cache(self): > + """ > + Clear all cached contents data. > + """ > + self._contents_insensitive = None > + self._reverse_key_map = None > + > + def keys(self): > + """ > + Iterate over all contents keys, which are > transformed to > + lowercase when appropriate, for use in > case-insensitive > + comparisons. > + @rtype: iterator > + @return: An iterator over all the contents keys > + """ > + return iter(self.getcontents()) > + > + def contains(self, key): > + """ > + Check if the given key is contained in the contents, > using > + case-insensitive comparison when appropriate. > + @param key: A filesystem path (including ROOT and > EPREFIX) > + @type key: str > + @rtype: bool > + @return: True if the given key is contained in the > contents, > + False otherwise > + """ > + return key in self.getcontents() > + > + def unmap_key(self, key): > + """ > + Map a key (from the keys method) back to its > case-preserved > + form. > + @param key: A filesystem path (including ROOT and > EPREFIX) > + @type key: str > + @rtype: str > + @return: The case-preserved form of key > + """ > + return key > + > + def _case_insensitive_init(self): > + """ > + Initialize data structures for case-insensitive > support. > + """ > + self._contents_insensitive = dict( > + (k.lower(), v) for k, v in > self.getcontents().items()) > + self._reverse_key_map = dict( > + (k.lower(), k) for k in self.getcontents()) > + > + def _keys_case_insensitive(self): > + if self._contents_insensitive is None: > + self._case_insensitive_init() > + return iter(self._contents_insensitive) > + > + _keys_case_insensitive.__doc__ = keys.__doc__ > + > + def _contains_case_insensitive(self, key): > + if self._contents_insensitive is None: > + self._case_insensitive_init() > + return key.lower() in self._contents_insensitive > + > + _contains_case_insensitive.__doc__ = contains.__doc__ > + > + def _unmap_key_case_insensitive(self, key): > + if self._reverse_key_map is None: > + self._case_insensitive_init() > + return self._reverse_key_map[key] > + > + _unmap_key_case_insensitive.__doc__ = unmap_key.__doc__ > diff --git a/pym/portage/dbapi/vartree.py > b/pym/portage/dbapi/vartree.py index 8b06f4c..81059b1 100644 > --- a/pym/portage/dbapi/vartree.py > +++ b/pym/portage/dbapi/vartree.py > @@ -69,6 +69,7 @@ from _emerge.EbuildPhase import EbuildPhase > from _emerge.emergelog import emergelog > from _emerge.MiscFunctionsProcess import MiscFunctionsProcess > from _emerge.SpawnProcess import SpawnProcess > +from ._ContentsCaseSensitivityManager import > ContentsCaseSensitivityManager > import errno > import fnmatch > @@ -1525,6 +1526,7 @@ class dblink(object): > # When necessary, this attribute is modified for > # compliance with RESTRICT=preserve-libs. > self._preserve_libs = "preserve-libs" in > mysettings.features > + self._contents = ContentsCaseSensitivityManager(self) > > def __hash__(self): > return hash(self._hash_key) > @@ -1612,6 +1614,7 @@ class dblink(object): > self.contentscache = None > self._contents_inodes = None > self._contents_basenames = None > + self._contents.clear_cache() > > def getcontents(self): > """ -- Brian Dolbec <dolsen> ^ permalink raw reply [flat|nested] 12+ messages in thread
end of thread, other threads:[~2014-11-17 22:09 UTC | newest] Thread overview: 12+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2014-11-13 1:22 [gentoo-portage-dev] [PATCH] FEATURES=case-insensitive-fs for bug #524236 Zac Medico 2014-11-13 10:29 ` Alexander Berntsen 2014-11-13 17:58 ` Zac Medico 2014-11-16 10:41 ` [gentoo-portage-dev] [PATCH 1/2] dblink: case insensitive support " Zac Medico 2014-11-16 10:41 ` [gentoo-portage-dev] [PATCH 2/2] FEATURES=case-insensitive-fs " Zac Medico 2014-11-16 18:11 ` [gentoo-portage-dev] [PATCH 1/2] dblink: case insensitive support " Brian Dolbec 2014-11-17 1:29 ` [gentoo-portage-dev] [PATCH v2 " Zac Medico 2014-11-17 1:29 ` [gentoo-portage-dev] [PATCH v2 2/2] FEATURES=case-insensitive-fs " Zac Medico 2014-11-17 4:58 ` [gentoo-portage-dev] [PATCH v2 1/2] dblink: case insensitive support " Brian Dolbec 2014-11-17 20:29 ` [gentoo-portage-dev] [PATCH v3 " Zac Medico 2014-11-17 20:29 ` [gentoo-portage-dev] [PATCH v3 2/2] FEATURES=case-insensitive-fs " Zac Medico 2014-11-17 22:09 ` [gentoo-portage-dev] [PATCH v3 1/2] dblink: case insensitive support " Brian Dolbec
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox