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 |