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 2/2] FEATURES=case-insensitive-fs for bug #524236
Date: Sun, 16 Nov 2014 10:42:28
Message-Id: 1416134515-31943-2-git-send-email-zmedico@gentoo.org
In Reply to: [gentoo-portage-dev] [PATCH 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 | 53 +++++++++++++++++++++++++++---------------
20 pym/portage/update.py | 6 +++--
21 pym/portage/util/__init__.py | 8 ++++++-
22 11 files changed, 80 insertions(+), 31 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 94eaed8..6bf2be5 100644
148 --- a/pym/_emerge/depgraph.py
149 +++ b/pym/_emerge/depgraph.py
150 @@ -7805,7 +7805,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..87085de 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 2d1003f..bf7985a 100644
190 --- a/pym/portage/dbapi/vartree.py
191 +++ b/pym/portage/dbapi/vartree.py
192 @@ -1051,13 +1051,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_iter():
206 self._add_path(x[eroot_len:], pkg_hash)
207
208 self._vardb._aux_cache["modified"].add(cpv)
209 @@ -1189,6 +1189,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 @@ -1205,6 +1207,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 @@ -1235,7 +1239,7 @@ class vardbapi(dbapi):
228 continue
229
230 if is_basename:
231 - for p in dblink(cpv).getcontents():
232 + for p in dblink(cpv)._contents_iter():
233 if os.path.basename(p) == name:
234 owners.append((cpv, p[len(root):]))
235 else:
236 @@ -1265,8 +1269,12 @@ class vardbapi(dbapi):
237 if not path_list:
238 return
239
240 + case_insensitive = "case-insensitive-fs" \
241 + in self._vardb.settings.features
242 path_info_list = []
243 for path in path_list:
244 + if case_insensitive:
245 + path = path.lower()
246 is_basename = os.sep != path[:1]
247 if is_basename:
248 name = path
249 @@ -1284,9 +1292,11 @@ class vardbapi(dbapi):
250 dblnk = self._vardb._dblink(cpv)
251 for path, name, is_basename in path_info_list:
252 if is_basename:
253 - for p in dblnk.getcontents():
254 + for p in dblnk._contents_iter():
255 if os.path.basename(p) == name:
256 - search_pkg.results.append((dblnk, p[len(root):]))
257 + search_pkg.results.append((dblnk,
258 + dblnk._contents_case_reverse_map(
259 + p)[len(root):]))
260 else:
261 if dblnk.isowner(path):
262 search_pkg.results.append((dblnk, path))
263 @@ -1551,7 +1561,9 @@ class dblink(object):
264 portage.util.shlex_split(
265 self.settings.get("CONFIG_PROTECT", "")),
266 portage.util.shlex_split(
267 - self.settings.get("CONFIG_PROTECT_MASK", "")))
268 + self.settings.get("CONFIG_PROTECT_MASK", "")),
269 + case_insensitive=("case-insensitive-fs"
270 + in self.settings.features))
271
272 return self._protect_obj
273
274 @@ -2804,15 +2816,18 @@ class dblink(object):
275 os_filename_arg.path.join(destroot,
276 filename.lstrip(os_filename_arg.path.sep)))
277
278 - pkgfiles = self.getcontents()
279 - if pkgfiles and destfile in pkgfiles:
280 - return destfile
281 - if pkgfiles:
282 + if "case-insensitive-fs" in self.settings.features:
283 + destfile = destfile.lower()
284 +
285 + if self._contents_contains(destfile):
286 + return self._contents_key(destfile)
287 +
288 + if self.getcontents():
289 basename = os_filename_arg.path.basename(destfile)
290 if self._contents_basenames is None:
291
292 try:
293 - for x in pkgfiles:
294 + for x in self._contents_iter():
295 _unicode_encode(x,
296 encoding=_encodings['merge'],
297 errors='strict')
298 @@ -2821,7 +2836,7 @@ class dblink(object):
299 # different value of sys.getfilesystemencoding(),
300 # so fall back to utf_8 if appropriate.
301 try:
302 - for x in pkgfiles:
303 + for x in self._contents_iter():
304 _unicode_encode(x,
305 encoding=_encodings['fs'],
306 errors='strict')
307 @@ -2831,7 +2846,7 @@ class dblink(object):
308 os = portage.os
309
310 self._contents_basenames = set(
311 - os.path.basename(x) for x in pkgfiles)
312 + os.path.basename(x) for x in self._contents_iter())
313 if basename not in self._contents_basenames:
314 # This is a shortcut that, in most cases, allows us to
315 # eliminate this package as an owner without the need
316 @@ -2852,7 +2867,7 @@ class dblink(object):
317
318 if os is _os_merge:
319 try:
320 - for x in pkgfiles:
321 + for x in self._contents_iter():
322 _unicode_encode(x,
323 encoding=_encodings['merge'],
324 errors='strict')
325 @@ -2861,7 +2876,7 @@ class dblink(object):
326 # different value of sys.getfilesystemencoding(),
327 # so fall back to utf_8 if appropriate.
328 try:
329 - for x in pkgfiles:
330 + for x in self._contents_iter():
331 _unicode_encode(x,
332 encoding=_encodings['fs'],
333 errors='strict')
334 @@ -2872,7 +2887,7 @@ class dblink(object):
335
336 self._contents_inodes = {}
337 parent_paths = set()
338 - for x in pkgfiles:
339 + for x in self._contents_iter():
340 p_path = os.path.dirname(x)
341 if p_path in parent_paths:
342 continue
343 @@ -2897,8 +2912,8 @@ class dblink(object):
344 if p_path_list:
345 for p_path in p_path_list:
346 x = os_filename_arg.path.join(p_path, basename)
347 - if x in pkgfiles:
348 - return x
349 + if self._contents_contains(x):
350 + return self._contents_key(x)
351
352 return False
353
354 diff --git a/pym/portage/update.py b/pym/portage/update.py
355 index df4e11b..83fc3d2 100644
356 --- a/pym/portage/update.py
357 +++ b/pym/portage/update.py
358 @@ -282,7 +282,8 @@ def parse_updates(mycontent):
359 myupd.append(mysplit)
360 return myupd, errors
361
362 -def update_config_files(config_root, protect, protect_mask, update_iter, match_callback = None):
363 +def update_config_files(config_root, protect, protect_mask, update_iter,
364 + match_callback=None, case_insensitive=False):
365 """Perform global updates on /etc/portage/package.*, /etc/portage/profile/package.*,
366 /etc/portage/profile/packages and /etc/portage/sets.
367 config_root - location of files to update
368 @@ -406,7 +407,8 @@ def update_config_files(config_root, protect, protect_mask, update_iter, match_c
369 sys.stdout.flush()
370
371 protect_obj = ConfigProtect(
372 - config_root, protect, protect_mask)
373 + config_root, protect, protect_mask,
374 + case_insensitive=case_insensitive)
375 for x in update_files:
376 updating_file = os.path.join(abs_user_config, x)
377 if protect_obj.isprotected(updating_file):
378 diff --git a/pym/portage/util/__init__.py b/pym/portage/util/__init__.py
379 index ad3a351..d0cca5b 100644
380 --- a/pym/portage/util/__init__.py
381 +++ b/pym/portage/util/__init__.py
382 @@ -1555,10 +1555,12 @@ class LazyItemsDict(UserDict):
383 return result
384
385 class ConfigProtect(object):
386 - def __init__(self, myroot, protect_list, mask_list):
387 + def __init__(self, myroot, protect_list, mask_list,
388 + case_insensitive=False):
389 self.myroot = myroot
390 self.protect_list = protect_list
391 self.mask_list = mask_list
392 + self.case_insensitive = case_insensitive
393 self.updateprotect()
394
395 def updateprotect(self):
396 @@ -1586,6 +1588,8 @@ class ConfigProtect(object):
397 for x in self.mask_list:
398 ppath = normalize_path(
399 os.path.join(self.myroot, x.lstrip(os.path.sep)))
400 + if self.case_insensitive:
401 + ppath = ppath.lower()
402 try:
403 """Use lstat so that anything, even a broken symlink can be
404 protected."""
405 @@ -1606,6 +1610,8 @@ class ConfigProtect(object):
406 masked = 0
407 protected = 0
408 sep = os.path.sep
409 + if self.case_insensitive:
410 + obj = obj.lower()
411 for ppath in self.protect:
412 if len(ppath) > masked and obj.startswith(ppath):
413 if ppath in self._dirs:
414 --
415 2.0.4