Gentoo Archives: gentoo-portage-dev

From: Zac Medico <zmedico@g.o>
To: gentoo-portage-dev@l.g.o
Cc: Zac Medico <zmedico@g.o>
Subject: [gentoo-portage-dev] [PATCH v2 2/2] FEATURES=case-insensitive-fs for bug #524236
Date: Mon, 17 Nov 2014 01:29:43
Message-Id: 1416187767-10240-2-git-send-email-zmedico@gentoo.org
In Reply to: [gentoo-portage-dev] [PATCH v2 1/2] dblink: case insensitive support for bug #524236 by Zac Medico
1 When case-insensitive-fs is enabled in FEATURES, the dblink.isowner
2 method, _owners_db class, and ConfigProtect class will be
3 case-insensitive. This causes the collision-protect and unmerge code
4 to behave correctly for a case-insensitive file system. If the file
5 system is case-insensitive but case-preserving, then case is preserved
6 in the CONTENTS data of installed packages.
7
8 X-Gentoo-Bug: 524236
9 X-Gentoo-Url: https://bugs.gentoo.org/show_bug.cgi?id=524236
10 ---
11 bin/dispatch-conf | 8 +++++-
12 bin/etc-update | 10 ++++++--
13 bin/portageq | 7 +++---
14 bin/quickpkg | 4 ++-
15 man/make.conf.5 | 6 +++++
16 pym/_emerge/depgraph.py | 4 ++-
17 pym/portage/_global_updates.py | 4 ++-
18 pym/portage/const.py | 1 +
19 pym/portage/dbapi/vartree.py | 57 +++++++++++++++++++++++++++---------------
20 pym/portage/update.py | 6 +++--
21 pym/portage/util/__init__.py | 8 +++++-
22 11 files changed, 83 insertions(+), 32 deletions(-)
23
24 diff --git a/bin/dispatch-conf b/bin/dispatch-conf
25 index 8058d6f..b679910 100755
26 --- a/bin/dispatch-conf
27 +++ b/bin/dispatch-conf
28 @@ -35,6 +35,10 @@ from portage.process import find_binary, spawn
29 FIND_EXTANT_CONFIGS = "find '%s' %s -name '._cfg????_%s' ! -name '.*~' ! -iname '.*.bak' -print"
30 DIFF_CONTENTS = "diff -Nu '%s' '%s'"
31
32 +if "case-insensitive-fs" in portage.settings.features:
33 + FIND_EXTANT_CONFIGS = FIND_EXTANT_CONFIGS.replace(
34 + "-name '._cfg", "-iname '._cfg")
35 +
36 # We need a secure scratch dir and python does silly verbose errors on the use of tempnam
37 oldmask = os.umask(0o077)
38 SCRATCH_DIR = None
39 @@ -152,7 +156,9 @@ class dispatch:
40 protect_obj = portage.util.ConfigProtect(
41 config_root, config_paths,
42 portage.util.shlex_split(
43 - portage.settings.get('CONFIG_PROTECT_MASK', '')))
44 + portage.settings.get('CONFIG_PROTECT_MASK', '')),
45 + case_insensitive=("case-insensitive-fs"
46 + in portage.settings.features))
47
48 #
49 # Remove new configs identical to current
50 diff --git a/bin/etc-update b/bin/etc-update
51 index 0307688..e0f7224 100755
52 --- a/bin/etc-update
53 +++ b/bin/etc-update
54 @@ -113,12 +113,15 @@ scan() {
55 [[ -d ${path%/*} ]] || continue
56 local name_opt=$(get_basename_find_opt "${path##*/}")
57 path="${path%/*}"
58 - find_opts=( -maxdepth 1 -name "$name_opt" )
59 + find_opts=( -maxdepth 1 )
60 else
61 # Do not traverse hidden directories such as .svn or .git.
62 local name_opt=$(get_basename_find_opt '*')
63 - find_opts=( -name '.*' -type d -prune -o -name "$name_opt" )
64 + find_opts=( -name '.*' -type d -prune -o )
65 fi
66 + ${case_insensitive} && \
67 + find_opts+=( -iname ) || find_opts+=( -name )
68 + find_opts+=( "$name_opt" )
69 find_opts+=( ! -name '.*~' ! -iname '.*.bak' -print )
70
71 if [ ! -w "${path}" ] ; then
72 @@ -743,6 +746,7 @@ fi
73
74 portage_vars=(
75 CONFIG_PROTECT{,_MASK}
76 + FEATURES
77 PORTAGE_CONFIGROOT
78 PORTAGE_INST_{G,U}ID
79 PORTAGE_TMPDIR
80 @@ -759,6 +763,8 @@ fi
81
82 export PORTAGE_TMPDIR
83 SCAN_PATHS=${*:-${CONFIG_PROTECT}}
84 +[[ " ${FEATURES} " == *" case-insensitive-fs "* ]] && \
85 + case_insensitive=true || case_insensitive=false
86
87 TMP="${PORTAGE_TMPDIR}/etc-update-$$"
88 trap "die terminated" SIGTERM
89 diff --git a/bin/portageq b/bin/portageq
90 index ef565d1..1618b44 100755
91 --- a/bin/portageq
92 +++ b/bin/portageq
93 @@ -379,8 +379,8 @@ def is_protected(argv):
94 protect = portage.util.shlex_split(settings.get("CONFIG_PROTECT", ""))
95 protect_mask = portage.util.shlex_split(
96 settings.get("CONFIG_PROTECT_MASK", ""))
97 - protect_obj = ConfigProtect(root, protect, protect_mask)
98 -
99 + protect_obj = ConfigProtect(root, protect, protect_mask,
100 + case_insensitive=("case-insensitive-fs" in settings.features))
101 if protect_obj.isprotected(f):
102 return 0
103 return 1
104 @@ -414,7 +414,8 @@ def filter_protected(argv):
105 protect = portage.util.shlex_split(settings.get("CONFIG_PROTECT", ""))
106 protect_mask = portage.util.shlex_split(
107 settings.get("CONFIG_PROTECT_MASK", ""))
108 - protect_obj = ConfigProtect(root, protect, protect_mask)
109 + protect_obj = ConfigProtect(root, protect, protect_mask,
110 + case_insensitive=("case-insensitive-fs" in settings.features))
111
112 errors = 0
113
114 diff --git a/bin/quickpkg b/bin/quickpkg
115 index cf75791..2c69a69 100755
116 --- a/bin/quickpkg
117 +++ b/bin/quickpkg
118 @@ -102,7 +102,9 @@ def quickpkg_atom(options, infos, arg, eout):
119 if not include_config:
120 confprot = ConfigProtect(eroot,
121 shlex_split(settings.get("CONFIG_PROTECT", "")),
122 - shlex_split(settings.get("CONFIG_PROTECT_MASK", "")))
123 + shlex_split(settings.get("CONFIG_PROTECT_MASK", "")),
124 + case_insensitive=("case-insensitive-fs"
125 + in settings.features))
126 def protect(filename):
127 if not confprot.isprotected(filename):
128 return False
129 diff --git a/man/make.conf.5 b/man/make.conf.5
130 index 84e894b..69d95fc 100644
131 --- a/man/make.conf.5
132 +++ b/man/make.conf.5
133 @@ -265,6 +265,12 @@ Build binary packages for just packages in the system set.
134 Enable a special progress indicator when \fBemerge\fR(1) is calculating
135 dependencies.
136 .TP
137 +.B case\-insensitive\-fs
138 +Use case\-insensitive file name comparisions when merging and unmerging
139 +files. Most users should not enable this feature, since most filesystems
140 +are case\-sensitive. You should only enable this feature if you are
141 +using portage to install files to a case\-insensitive filesystem.
142 +.TP
143 .B ccache
144 Enable portage support for the ccache package. If the ccache dir is not
145 present in the user's environment, then portage will default to
146 diff --git a/pym/_emerge/depgraph.py b/pym/_emerge/depgraph.py
147 index 2a839d0..6f1910d 100644
148 --- a/pym/_emerge/depgraph.py
149 +++ b/pym/_emerge/depgraph.py
150 @@ -7813,7 +7813,9 @@ class depgraph(object):
151 settings = self._frozen_config.roots[root].settings
152 protect_obj[root] = ConfigProtect(settings["EROOT"], \
153 shlex_split(settings.get("CONFIG_PROTECT", "")),
154 - shlex_split(settings.get("CONFIG_PROTECT_MASK", "")))
155 + shlex_split(settings.get("CONFIG_PROTECT_MASK", "")),
156 + case_insensitive=("case-insensitive-fs"
157 + in settings.features))
158
159 def write_changes(root, changes, file_to_write_to):
160 file_contents = None
161 diff --git a/pym/portage/_global_updates.py b/pym/portage/_global_updates.py
162 index 17dc080..81ee484 100644
163 --- a/pym/portage/_global_updates.py
164 +++ b/pym/portage/_global_updates.py
165 @@ -208,7 +208,9 @@ def _do_global_updates(trees, prev_mtimes, quiet=False, if_mtime_changed=True):
166 update_config_files(root,
167 shlex_split(mysettings.get("CONFIG_PROTECT", "")),
168 shlex_split(mysettings.get("CONFIG_PROTECT_MASK", "")),
169 - repo_map, match_callback=_config_repo_match)
170 + repo_map, match_callback=_config_repo_match,
171 + case_insensitive="case-insensitive-fs"
172 + in mysettings.features)
173
174 # The above global updates proceed quickly, so they
175 # are considered a single mtimedb transaction.
176 diff --git a/pym/portage/const.py b/pym/portage/const.py
177 index d472075..febdb4a 100644
178 --- a/pym/portage/const.py
179 +++ b/pym/portage/const.py
180 @@ -125,6 +125,7 @@ SUPPORTED_FEATURES = frozenset([
181 "buildpkg",
182 "buildsyspkg",
183 "candy",
184 + "case-insensitive-fs",
185 "ccache",
186 "cgroup",
187 "chflags",
188 diff --git a/pym/portage/dbapi/vartree.py b/pym/portage/dbapi/vartree.py
189 index 22f41d0..faee773 100644
190 --- a/pym/portage/dbapi/vartree.py
191 +++ b/pym/portage/dbapi/vartree.py
192 @@ -1052,13 +1052,13 @@ class vardbapi(dbapi):
193
194 def add(self, cpv):
195 eroot_len = len(self._vardb._eroot)
196 - contents = self._vardb._dblink(cpv).getcontents()
197 pkg_hash = self._hash_pkg(cpv)
198 - if not contents:
199 + db = self._vardb._dblink(cpv)
200 + if not db.getcontents():
201 # Empty path is a code used to represent empty contents.
202 self._add_path("", pkg_hash)
203
204 - for x in contents:
205 + for x in db._contents_keys():
206 self._add_path(x[eroot_len:], pkg_hash)
207
208 self._vardb._aux_cache["modified"].add(cpv)
209 @@ -1190,6 +1190,8 @@ class vardbapi(dbapi):
210 hash_pkg = owners_cache._hash_pkg
211 hash_str = owners_cache._hash_str
212 base_names = self._vardb._aux_cache["owners"]["base_names"]
213 + case_insensitive = "case-insensitive-fs" \
214 + in vardb.settings.features
215
216 dblink_cache = {}
217
218 @@ -1206,6 +1208,8 @@ class vardbapi(dbapi):
219 while path_iter:
220
221 path = path_iter.pop()
222 + if case_insensitive:
223 + path = path.lower()
224 is_basename = os.sep != path[:1]
225 if is_basename:
226 name = path
227 @@ -1236,9 +1240,11 @@ class vardbapi(dbapi):
228 continue
229
230 if is_basename:
231 - for p in dblink(cpv).getcontents():
232 + for p in dblink(cpv)._contents_keys():
233 if os.path.basename(p) == name:
234 - owners.append((cpv, p[len(root):]))
235 + owners.append((cpv,
236 + dblink(cpv)._contents_unmap_key(
237 + p)[len(root):]))
238 else:
239 if dblink(cpv).isowner(path):
240 owners.append((cpv, path))
241 @@ -1266,8 +1272,12 @@ class vardbapi(dbapi):
242 if not path_list:
243 return
244
245 + case_insensitive = "case-insensitive-fs" \
246 + in self._vardb.settings.features
247 path_info_list = []
248 for path in path_list:
249 + if case_insensitive:
250 + path = path.lower()
251 is_basename = os.sep != path[:1]
252 if is_basename:
253 name = path
254 @@ -1285,9 +1295,11 @@ class vardbapi(dbapi):
255 dblnk = self._vardb._dblink(cpv)
256 for path, name, is_basename in path_info_list:
257 if is_basename:
258 - for p in dblnk.getcontents():
259 + for p in dblnk._contents_keys():
260 if os.path.basename(p) == name:
261 - search_pkg.results.append((dblnk, p[len(root):]))
262 + search_pkg.results.append((dblnk,
263 + dblnk._contents_unmap_key(
264 + p)[len(root):]))
265 else:
266 if dblnk.isowner(path):
267 search_pkg.results.append((dblnk, path))
268 @@ -1547,7 +1559,9 @@ class dblink(object):
269 portage.util.shlex_split(
270 self.settings.get("CONFIG_PROTECT", "")),
271 portage.util.shlex_split(
272 - self.settings.get("CONFIG_PROTECT_MASK", "")))
273 + self.settings.get("CONFIG_PROTECT_MASK", "")),
274 + case_insensitive=("case-insensitive-fs"
275 + in self.settings.features))
276
277 return self._protect_obj
278
279 @@ -2769,15 +2783,18 @@ class dblink(object):
280 os_filename_arg.path.join(destroot,
281 filename.lstrip(os_filename_arg.path.sep)))
282
283 - pkgfiles = self.getcontents()
284 - if pkgfiles and destfile in pkgfiles:
285 - return destfile
286 - if pkgfiles:
287 + if "case-insensitive-fs" in self.settings.features:
288 + destfile = destfile.lower()
289 +
290 + if self._contents_contains(destfile):
291 + return self._contents_unmap_key(destfile)
292 +
293 + if self.getcontents():
294 basename = os_filename_arg.path.basename(destfile)
295 if self._contents_basenames is None:
296
297 try:
298 - for x in pkgfiles:
299 + for x in self._contents_keys():
300 _unicode_encode(x,
301 encoding=_encodings['merge'],
302 errors='strict')
303 @@ -2786,7 +2803,7 @@ class dblink(object):
304 # different value of sys.getfilesystemencoding(),
305 # so fall back to utf_8 if appropriate.
306 try:
307 - for x in pkgfiles:
308 + for x in self._contents_keys():
309 _unicode_encode(x,
310 encoding=_encodings['fs'],
311 errors='strict')
312 @@ -2796,7 +2813,7 @@ class dblink(object):
313 os = portage.os
314
315 self._contents_basenames = set(
316 - os.path.basename(x) for x in pkgfiles)
317 + os.path.basename(x) for x in self._contents_keys())
318 if basename not in self._contents_basenames:
319 # This is a shortcut that, in most cases, allows us to
320 # eliminate this package as an owner without the need
321 @@ -2817,7 +2834,7 @@ class dblink(object):
322
323 if os is _os_merge:
324 try:
325 - for x in pkgfiles:
326 + for x in self._contents_keys():
327 _unicode_encode(x,
328 encoding=_encodings['merge'],
329 errors='strict')
330 @@ -2826,7 +2843,7 @@ class dblink(object):
331 # different value of sys.getfilesystemencoding(),
332 # so fall back to utf_8 if appropriate.
333 try:
334 - for x in pkgfiles:
335 + for x in self._contents_keys():
336 _unicode_encode(x,
337 encoding=_encodings['fs'],
338 errors='strict')
339 @@ -2837,7 +2854,7 @@ class dblink(object):
340
341 self._contents_inodes = {}
342 parent_paths = set()
343 - for x in pkgfiles:
344 + for x in self._contents_keys():
345 p_path = os.path.dirname(x)
346 if p_path in parent_paths:
347 continue
348 @@ -2862,8 +2879,8 @@ class dblink(object):
349 if p_path_list:
350 for p_path in p_path_list:
351 x = os_filename_arg.path.join(p_path, basename)
352 - if x in pkgfiles:
353 - return x
354 + if self._contents_contains(x):
355 + return self._contents_unmap_key(x)
356
357 return False
358
359 diff --git a/pym/portage/update.py b/pym/portage/update.py
360 index df4e11b..83fc3d2 100644
361 --- a/pym/portage/update.py
362 +++ b/pym/portage/update.py
363 @@ -282,7 +282,8 @@ def parse_updates(mycontent):
364 myupd.append(mysplit)
365 return myupd, errors
366
367 -def update_config_files(config_root, protect, protect_mask, update_iter, match_callback = None):
368 +def update_config_files(config_root, protect, protect_mask, update_iter,
369 + match_callback=None, case_insensitive=False):
370 """Perform global updates on /etc/portage/package.*, /etc/portage/profile/package.*,
371 /etc/portage/profile/packages and /etc/portage/sets.
372 config_root - location of files to update
373 @@ -406,7 +407,8 @@ def update_config_files(config_root, protect, protect_mask, update_iter, match_c
374 sys.stdout.flush()
375
376 protect_obj = ConfigProtect(
377 - config_root, protect, protect_mask)
378 + config_root, protect, protect_mask,
379 + case_insensitive=case_insensitive)
380 for x in update_files:
381 updating_file = os.path.join(abs_user_config, x)
382 if protect_obj.isprotected(updating_file):
383 diff --git a/pym/portage/util/__init__.py b/pym/portage/util/__init__.py
384 index ad3a351..d0cca5b 100644
385 --- a/pym/portage/util/__init__.py
386 +++ b/pym/portage/util/__init__.py
387 @@ -1555,10 +1555,12 @@ class LazyItemsDict(UserDict):
388 return result
389
390 class ConfigProtect(object):
391 - def __init__(self, myroot, protect_list, mask_list):
392 + def __init__(self, myroot, protect_list, mask_list,
393 + case_insensitive=False):
394 self.myroot = myroot
395 self.protect_list = protect_list
396 self.mask_list = mask_list
397 + self.case_insensitive = case_insensitive
398 self.updateprotect()
399
400 def updateprotect(self):
401 @@ -1586,6 +1588,8 @@ class ConfigProtect(object):
402 for x in self.mask_list:
403 ppath = normalize_path(
404 os.path.join(self.myroot, x.lstrip(os.path.sep)))
405 + if self.case_insensitive:
406 + ppath = ppath.lower()
407 try:
408 """Use lstat so that anything, even a broken symlink can be
409 protected."""
410 @@ -1606,6 +1610,8 @@ class ConfigProtect(object):
411 masked = 0
412 protected = 0
413 sep = os.path.sep
414 + if self.case_insensitive:
415 + obj = obj.lower()
416 for ppath in self.protect:
417 if len(ppath) > masked and obj.startswith(ppath):
418 if ppath in self._dirs:
419 --
420 2.0.4