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 v3 2/2] FEATURES=case-insensitive-fs for bug #524236
Date: Mon, 17 Nov 2014 20:30:02
Message-Id: 1416256178-18009-2-git-send-email-zmedico@gentoo.org
In Reply to: [gentoo-portage-dev] [PATCH v3 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 | 71 +++++++++++++++++++++++++++---------------
20 pym/portage/update.py | 6 ++--
21 pym/portage/util/__init__.py | 8 ++++-
22 11 files changed, 92 insertions(+), 37 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 6a42bfd..2c4f548 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 81059b1..0fd1bd9 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,12 +1240,16 @@ 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, dblink(cpv).
236 + _contents.unmap_key(
237 + p)[len(root):]))
238 else:
239 - if dblink(cpv).isowner(path):
240 - owners.append((cpv, path))
241 + key = dblink(cpv)._match_contents(path)
242 + if key is not False:
243 + owners.append(
244 + (cpv, key[len(root):]))
245
246 except StopIteration:
247 path_iter.append(path)
248 @@ -1266,8 +1274,12 @@ class vardbapi(dbapi):
249 if not path_list:
250 return
251
252 + case_insensitive = "case-insensitive-fs" \
253 + in self._vardb.settings.features
254 path_info_list = []
255 for path in path_list:
256 + if case_insensitive:
257 + path = path.lower()
258 is_basename = os.sep != path[:1]
259 if is_basename:
260 name = path
261 @@ -1285,12 +1297,16 @@ class vardbapi(dbapi):
262 dblnk = self._vardb._dblink(cpv)
263 for path, name, is_basename in path_info_list:
264 if is_basename:
265 - for p in dblnk.getcontents():
266 + for p in dblnk._contents.keys():
267 if os.path.basename(p) == name:
268 - search_pkg.results.append((dblnk, p[len(root):]))
269 + search_pkg.results.append((dblnk,
270 + dblnk._contents.unmap_key(
271 + p)[len(root):]))
272 else:
273 - if dblnk.isowner(path):
274 - search_pkg.results.append((dblnk, path))
275 + key = dblnk._match_contents(path)
276 + if key is not False:
277 + search_pkg.results.append(
278 + (dblnk, key[len(root):]))
279 search_pkg.complete = True
280 return False
281
282 @@ -1542,7 +1558,9 @@ class dblink(object):
283 portage.util.shlex_split(
284 self.settings.get("CONFIG_PROTECT", "")),
285 portage.util.shlex_split(
286 - self.settings.get("CONFIG_PROTECT_MASK", "")))
287 + self.settings.get("CONFIG_PROTECT_MASK", "")),
288 + case_insensitive=("case-insensitive-fs"
289 + in self.settings.features))
290
291 return self._protect_obj
292
293 @@ -1620,9 +1638,9 @@ class dblink(object):
294 """
295 Get the installed files of a given package (aka what that package installed)
296 """
297 - contents_file = os.path.join(self.dbdir, "CONTENTS")
298 if self.contentscache is not None:
299 return self.contentscache
300 + contents_file = os.path.join(self.dbdir, "CONTENTS")
301 pkgfiles = {}
302 try:
303 with io.open(_unicode_encode(contents_file,
304 @@ -2764,15 +2782,18 @@ class dblink(object):
305 os_filename_arg.path.join(destroot,
306 filename.lstrip(os_filename_arg.path.sep)))
307
308 - pkgfiles = self.getcontents()
309 - if pkgfiles and destfile in pkgfiles:
310 - return destfile
311 - if pkgfiles:
312 + if "case-insensitive-fs" in self.settings.features:
313 + destfile = destfile.lower()
314 +
315 + if self._contents.contains(destfile):
316 + return self._contents.unmap_key(destfile)
317 +
318 + if self.getcontents():
319 basename = os_filename_arg.path.basename(destfile)
320 if self._contents_basenames is None:
321
322 try:
323 - for x in pkgfiles:
324 + for x in self._contents.keys():
325 _unicode_encode(x,
326 encoding=_encodings['merge'],
327 errors='strict')
328 @@ -2781,7 +2802,7 @@ class dblink(object):
329 # different value of sys.getfilesystemencoding(),
330 # so fall back to utf_8 if appropriate.
331 try:
332 - for x in pkgfiles:
333 + for x in self._contents.keys():
334 _unicode_encode(x,
335 encoding=_encodings['fs'],
336 errors='strict')
337 @@ -2791,7 +2812,7 @@ class dblink(object):
338 os = portage.os
339
340 self._contents_basenames = set(
341 - os.path.basename(x) for x in pkgfiles)
342 + os.path.basename(x) for x in self._contents.keys())
343 if basename not in self._contents_basenames:
344 # This is a shortcut that, in most cases, allows us to
345 # eliminate this package as an owner without the need
346 @@ -2812,7 +2833,7 @@ class dblink(object):
347
348 if os is _os_merge:
349 try:
350 - for x in pkgfiles:
351 + for x in self._contents.keys():
352 _unicode_encode(x,
353 encoding=_encodings['merge'],
354 errors='strict')
355 @@ -2821,7 +2842,7 @@ class dblink(object):
356 # different value of sys.getfilesystemencoding(),
357 # so fall back to utf_8 if appropriate.
358 try:
359 - for x in pkgfiles:
360 + for x in self._contents.keys():
361 _unicode_encode(x,
362 encoding=_encodings['fs'],
363 errors='strict')
364 @@ -2832,7 +2853,7 @@ class dblink(object):
365
366 self._contents_inodes = {}
367 parent_paths = set()
368 - for x in pkgfiles:
369 + for x in self._contents.keys():
370 p_path = os.path.dirname(x)
371 if p_path in parent_paths:
372 continue
373 @@ -2857,8 +2878,8 @@ class dblink(object):
374 if p_path_list:
375 for p_path in p_path_list:
376 x = os_filename_arg.path.join(p_path, basename)
377 - if x in pkgfiles:
378 - return x
379 + if self._contents.contains(x):
380 + return self._contents.unmap_key(x)
381
382 return False
383
384 diff --git a/pym/portage/update.py b/pym/portage/update.py
385 index df4e11b..83fc3d2 100644
386 --- a/pym/portage/update.py
387 +++ b/pym/portage/update.py
388 @@ -282,7 +282,8 @@ def parse_updates(mycontent):
389 myupd.append(mysplit)
390 return myupd, errors
391
392 -def update_config_files(config_root, protect, protect_mask, update_iter, match_callback = None):
393 +def update_config_files(config_root, protect, protect_mask, update_iter,
394 + match_callback=None, case_insensitive=False):
395 """Perform global updates on /etc/portage/package.*, /etc/portage/profile/package.*,
396 /etc/portage/profile/packages and /etc/portage/sets.
397 config_root - location of files to update
398 @@ -406,7 +407,8 @@ def update_config_files(config_root, protect, protect_mask, update_iter, match_c
399 sys.stdout.flush()
400
401 protect_obj = ConfigProtect(
402 - config_root, protect, protect_mask)
403 + config_root, protect, protect_mask,
404 + case_insensitive=case_insensitive)
405 for x in update_files:
406 updating_file = os.path.join(abs_user_config, x)
407 if protect_obj.isprotected(updating_file):
408 diff --git a/pym/portage/util/__init__.py b/pym/portage/util/__init__.py
409 index ad3a351..d0cca5b 100644
410 --- a/pym/portage/util/__init__.py
411 +++ b/pym/portage/util/__init__.py
412 @@ -1555,10 +1555,12 @@ class LazyItemsDict(UserDict):
413 return result
414
415 class ConfigProtect(object):
416 - def __init__(self, myroot, protect_list, mask_list):
417 + def __init__(self, myroot, protect_list, mask_list,
418 + case_insensitive=False):
419 self.myroot = myroot
420 self.protect_list = protect_list
421 self.mask_list = mask_list
422 + self.case_insensitive = case_insensitive
423 self.updateprotect()
424
425 def updateprotect(self):
426 @@ -1586,6 +1588,8 @@ class ConfigProtect(object):
427 for x in self.mask_list:
428 ppath = normalize_path(
429 os.path.join(self.myroot, x.lstrip(os.path.sep)))
430 + if self.case_insensitive:
431 + ppath = ppath.lower()
432 try:
433 """Use lstat so that anything, even a broken symlink can be
434 protected."""
435 @@ -1606,6 +1610,8 @@ class ConfigProtect(object):
436 masked = 0
437 protected = 0
438 sep = os.path.sep
439 + if self.case_insensitive:
440 + obj = obj.lower()
441 for ppath in self.protect:
442 if len(ppath) > masked and obj.startswith(ppath):
443 if ppath in self._dirs:
444 --
445 2.0.4