1
2
3
4 __all__ = ['apply_permissions', 'apply_recursive_permissions',
5 'apply_secpass_permissions', 'apply_stat_permissions', 'atomic_ofstream',
6 'cmp_sort_key', 'ConfigProtect', 'dump_traceback', 'ensure_dirs',
7 'find_updated_config_files', 'getconfig', 'getlibpaths', 'grabdict',
8 'grabdict_package', 'grabfile', 'grabfile_package', 'grablines',
9 'initialize_logger', 'LazyItemsDict', 'map_dictlist_vals',
10 'new_protect_filename', 'normalize_path', 'pickle_read', 'stack_dictlist',
11 'stack_dicts', 'stack_lists', 'unique_array', 'unique_everseen', 'varexpand',
12 'write_atomic', 'writedict', 'writemsg', 'writemsg_level', 'writemsg_stdout']
13
14 from copy import deepcopy
15 import errno
16 import io
17 try:
18 from itertools import filterfalse
19 except ImportError:
20 from itertools import ifilterfalse as filterfalse
21 import logging
22 import re
23 import shlex
24 import stat
25 import string
26 import sys
27 import traceback
28 import glob
29
30 import portage
31 portage.proxy.lazyimport.lazyimport(globals(),
32 'pickle',
33 'portage.dep:Atom',
34 'subprocess',
35 )
36
37 from portage import os
38 from portage import _encodings
39 from portage import _os_merge
40 from portage import _unicode_encode
41 from portage import _unicode_decode
42 from portage.const import VCS_DIRS
43 from portage.exception import InvalidAtom, PortageException, FileNotFound, \
44 OperationNotPermitted, ParseError, PermissionDenied, ReadOnlyFileSystem
45 from portage.localization import _
46 from portage.proxy.objectproxy import ObjectProxy
47 from portage.cache.mappings import UserDict
48
49 if sys.hexversion >= 0x3000000:
50 _unicode = str
51 else:
52 _unicode = unicode
53
54 noiselimit = 0
55
57 """Sets up basic logging of portage activities
58 Args:
59 level: the level to emit messages at ('info', 'debug', 'warning' ...)
60 Returns:
61 None
62 """
63 logging.basicConfig(level=logging.WARN, format='[%(levelname)-4s] %(message)s')
64
65 -def writemsg(mystr,noiselevel=0,fd=None):
66 """Prints out warning and debug messages based on the noiselimit setting"""
67 global noiselimit
68 if fd is None:
69 fd = sys.stderr
70 if noiselevel <= noiselimit:
71
72 if isinstance(fd, io.StringIO):
73 mystr = _unicode_decode(mystr,
74 encoding=_encodings['content'], errors='replace')
75 else:
76 mystr = _unicode_encode(mystr,
77 encoding=_encodings['stdio'], errors='backslashreplace')
78 if sys.hexversion >= 0x3000000 and fd in (sys.stdout, sys.stderr):
79 fd = fd.buffer
80 fd.write(mystr)
81 fd.flush()
82
84 """Prints messages stdout based on the noiselimit setting"""
85 writemsg(mystr, noiselevel=noiselevel, fd=sys.stdout)
86
88 """
89 Show a message for the given level as defined by the logging module
90 (default is 0). When level >= logging.WARNING then the message is
91 sent to stderr, otherwise it is sent to stdout. The noiselevel is
92 passed directly to writemsg().
93
94 @type msg: str
95 @param msg: a message string, including newline if appropriate
96 @type level: int
97 @param level: a numeric logging level (see the logging module)
98 @type noiselevel: int
99 @param noiselevel: passed directly to writemsg
100 """
101 if level >= logging.WARNING:
102 fd = sys.stderr
103 else:
104 fd = sys.stdout
105 writemsg(msg, noiselevel=noiselevel, fd=fd)
106
108 """
109 os.path.normpath("//foo") returns "//foo" instead of "/foo"
110 We dislike this behavior so we create our own normpath func
111 to fix it.
112 """
113 if sys.hexversion >= 0x3000000 and isinstance(mypath, bytes):
114 path_sep = os.path.sep.encode()
115 else:
116 path_sep = os.path.sep
117
118 if mypath.startswith(path_sep):
119
120 return os.path.normpath(2*path_sep + mypath)
121 else:
122 return os.path.normpath(mypath)
123
124 -def grabfile(myfilename, compat_level=0, recursive=0, remember_source_file=False):
125 """This function grabs the lines in a file, normalizes whitespace and returns lines in a list; if a line
126 begins with a #, it is ignored, as are empty lines"""
127
128 mylines=grablines(myfilename, recursive, remember_source_file=True)
129 newlines=[]
130
131 for x, source_file in mylines:
132
133
134 myline = x.split()
135 if x and x[0] != "#":
136 mylinetemp = []
137 for item in myline:
138 if item[:1] != "#":
139 mylinetemp.append(item)
140 else:
141 break
142 myline = mylinetemp
143
144 myline = " ".join(myline)
145 if not myline:
146 continue
147 if myline[0]=="#":
148
149
150 mylinetest = myline.split("<==",1)
151 if len(mylinetest) == 2:
152 myline_potential = mylinetest[1]
153 mylinetest = mylinetest[0].split("##COMPAT==>")
154 if len(mylinetest) == 2:
155 if compat_level >= int(mylinetest[1]):
156
157 newlines.append(myline_potential)
158 continue
159 else:
160 continue
161 if remember_source_file:
162 newlines.append((myline, source_file))
163 else:
164 newlines.append(myline)
165 return newlines
166
168 """Performs a function on each value of each key in a dictlist.
169 Returns a new dictlist."""
170 new_dl = {}
171 for key in myDict:
172 new_dl[key] = []
173 new_dl[key] = [func(x) for x in myDict[key]]
174 return new_dl
175
176 -def stack_dictlist(original_dicts, incremental=0, incrementals=[], ignore_none=0):
177 """
178 Stacks an array of dict-types into one array. Optionally merging or
179 overwriting matching key/value pairs for the dict[key]->list.
180 Returns a single dict. Higher index in lists is preferenced.
181
182 Example usage:
183 >>> from portage.util import stack_dictlist
184 >>> print stack_dictlist( [{'a':'b'},{'x':'y'}])
185 >>> {'a':'b','x':'y'}
186 >>> print stack_dictlist( [{'a':'b'},{'a':'c'}], incremental = True )
187 >>> {'a':['b','c'] }
188 >>> a = {'KEYWORDS':['x86','alpha']}
189 >>> b = {'KEYWORDS':['-x86']}
190 >>> print stack_dictlist( [a,b] )
191 >>> { 'KEYWORDS':['x86','alpha','-x86']}
192 >>> print stack_dictlist( [a,b], incremental=True)
193 >>> { 'KEYWORDS':['alpha'] }
194 >>> print stack_dictlist( [a,b], incrementals=['KEYWORDS'])
195 >>> { 'KEYWORDS':['alpha'] }
196
197 @param original_dicts a list of (dictionary objects or None)
198 @type list
199 @param incremental True or false depending on whether new keys should overwrite
200 keys which already exist.
201 @type boolean
202 @param incrementals A list of items that should be incremental (-foo removes foo from
203 the returned dict).
204 @type list
205 @param ignore_none Appears to be ignored, but probably was used long long ago.
206 @type boolean
207
208 """
209 final_dict = {}
210 for mydict in original_dicts:
211 if mydict is None:
212 continue
213 for y in mydict:
214 if not y in final_dict:
215 final_dict[y] = []
216
217 for thing in mydict[y]:
218 if thing:
219 if incremental or y in incrementals:
220 if thing == "-*":
221 final_dict[y] = []
222 continue
223 elif thing[:1] == '-':
224 try:
225 final_dict[y].remove(thing[1:])
226 except ValueError:
227 pass
228 continue
229 if thing not in final_dict[y]:
230 final_dict[y].append(thing)
231 if y in final_dict and not final_dict[y]:
232 del final_dict[y]
233 return final_dict
234
235 -def stack_dicts(dicts, incremental=0, incrementals=[], ignore_none=0):
236 """Stacks an array of dict-types into one array. Optionally merging or
237 overwriting matching key/value pairs for the dict[key]->string.
238 Returns a single dict."""
239 final_dict = {}
240 for mydict in dicts:
241 if not mydict:
242 continue
243 for k, v in mydict.items():
244 if k in final_dict and (incremental or (k in incrementals)):
245 final_dict[k] += " " + v
246 else:
247 final_dict[k] = v
248 return final_dict
249
250 -def append_repo(atom_list, repo_name, remember_source_file=False):
251 """
252 Takes a list of valid atoms without repo spec and appends ::repo_name.
253 If an atom already has a repo part, then it is preserved (see bug #461948).
254 """
255 if remember_source_file:
256 return [(atom.repo is not None and atom or atom.with_repo(repo_name), source) \
257 for atom, source in atom_list]
258 else:
259 return [atom.repo is not None and atom or atom.with_repo(repo_name) \
260 for atom in atom_list]
261
262 -def stack_lists(lists, incremental=1, remember_source_file=False,
263 warn_for_unmatched_removal=False, strict_warn_for_unmatched_removal=False, ignore_repo=False):
264 """Stacks an array of list-types into one array. Optionally removing
265 distinct values using '-value' notation. Higher index is preferenced.
266
267 all elements must be hashable."""
268 matched_removals = set()
269 unmatched_removals = {}
270 new_list = {}
271 for sub_list in lists:
272 for token in sub_list:
273 token_key = token
274 if remember_source_file:
275 token, source_file = token
276 else:
277 source_file = False
278
279 if token is None:
280 continue
281
282 if incremental:
283 if token == "-*":
284 new_list.clear()
285 elif token[:1] == '-':
286 matched = False
287 if ignore_repo and not "::" in token:
288
289 to_be_removed = []
290 token_slice = token[1:]
291 for atom in new_list:
292 atom_without_repo = atom
293 if atom.repo is not None:
294
295
296
297 atom_without_repo = \
298 atom.replace("::" + atom.repo, "", 1)
299 if atom_without_repo == token_slice:
300 to_be_removed.append(atom)
301 if to_be_removed:
302 matched = True
303 for atom in to_be_removed:
304 new_list.pop(atom)
305 else:
306 try:
307 new_list.pop(token[1:])
308 matched = True
309 except KeyError:
310 pass
311
312 if not matched:
313 if source_file and \
314 (strict_warn_for_unmatched_removal or \
315 token_key not in matched_removals):
316 unmatched_removals.setdefault(source_file, set()).add(token)
317 else:
318 matched_removals.add(token_key)
319 else:
320 new_list[token] = source_file
321 else:
322 new_list[token] = source_file
323
324 if warn_for_unmatched_removal:
325 for source_file, tokens in unmatched_removals.items():
326 if len(tokens) > 3:
327 selected = [tokens.pop(), tokens.pop(), tokens.pop()]
328 writemsg(_("--- Unmatched removal atoms in %s: %s and %s more\n") % \
329 (source_file, ", ".join(selected), len(tokens)),
330 noiselevel=-1)
331 else:
332 writemsg(_("--- Unmatched removal atom(s) in %s: %s\n") % (source_file, ", ".join(tokens)),
333 noiselevel=-1)
334
335 if remember_source_file:
336 return list(new_list.items())
337 else:
338 return list(new_list)
339
340 -def grabdict(myfilename, juststrings=0, empty=0, recursive=0, incremental=1):
341 """
342 This function grabs the lines in a file, normalizes whitespace and returns lines in a dictionary
343
344 @param myfilename: file to process
345 @type myfilename: string (path)
346 @param juststrings: only return strings
347 @type juststrings: Boolean (integer)
348 @param empty: Ignore certain lines
349 @type empty: Boolean (integer)
350 @param recursive: Recursively grab ( support for /etc/portage/package.keywords/* and friends )
351 @type recursive: Boolean (integer)
352 @param incremental: Append to the return list, don't overwrite
353 @type incremental: Boolean (integer)
354 @rtype: Dictionary
355 @return:
356 1. Returns the lines in a file in a dictionary, for example:
357 'sys-apps/portage x86 amd64 ppc'
358 would return
359 { "sys-apps/portage" : [ 'x86', 'amd64', 'ppc' ]
360 """
361 newdict={}
362 for x in grablines(myfilename, recursive):
363
364
365 if x[0] == "#":
366 continue
367 myline=x.split()
368 mylinetemp = []
369 for item in myline:
370 if item[:1] != "#":
371 mylinetemp.append(item)
372 else:
373 break
374 myline = mylinetemp
375 if len(myline) < 2 and empty == 0:
376 continue
377 if len(myline) < 1 and empty == 1:
378 continue
379 if incremental:
380 newdict.setdefault(myline[0], []).extend(myline[1:])
381 else:
382 newdict[myline[0]] = myline[1:]
383 if juststrings:
384 for k, v in newdict.items():
385 newdict[k] = " ".join(v)
386 return newdict
387
388 _eapi_cache = {}
389
391 """
392 Read the 'eapi' file from the directory 'filename' is in.
393 Returns "0" if the file is not present or invalid.
394 """
395 eapi_file = os.path.join(os.path.dirname(filename), "eapi")
396 try:
397 eapi = _eapi_cache[eapi_file]
398 except KeyError:
399 pass
400 else:
401 if eapi is None:
402 return default
403 return eapi
404
405 eapi = None
406 try:
407 f = io.open(_unicode_encode(eapi_file,
408 encoding=_encodings['fs'], errors='strict'),
409 mode='r', encoding=_encodings['repo.content'], errors='replace')
410 lines = f.readlines()
411 if len(lines) == 1:
412 eapi = lines[0].rstrip("\n")
413 else:
414 writemsg(_("--- Invalid 'eapi' file (doesn't contain exactly one line): %s\n") % (eapi_file),
415 noiselevel=-1)
416 f.close()
417 except IOError:
418 pass
419
420 _eapi_cache[eapi_file] = eapi
421 if eapi is None:
422 return default
423 return eapi
424
425 -def grabdict_package(myfilename, juststrings=0, recursive=0, allow_wildcard=False, allow_repo=False,
426 verify_eapi=False, eapi=None):
427 """ Does the same thing as grabdict except it validates keys
428 with isvalidatom()"""
429 pkgs=grabdict(myfilename, juststrings, empty=1, recursive=recursive)
430 if not pkgs:
431 return pkgs
432 if verify_eapi and eapi is None:
433 eapi = read_corresponding_eapi_file(myfilename)
434
435
436
437
438 atoms = {}
439 for k, v in pkgs.items():
440 try:
441 k = Atom(k, allow_wildcard=allow_wildcard, allow_repo=allow_repo, eapi=eapi)
442 except InvalidAtom as e:
443 writemsg(_("--- Invalid atom in %s: %s\n") % (myfilename, e),
444 noiselevel=-1)
445 else:
446 atoms[k] = v
447 return atoms
448
449 -def grabfile_package(myfilename, compatlevel=0, recursive=0, allow_wildcard=False, allow_repo=False,
450 remember_source_file=False, verify_eapi=False, eapi=None):
451
452 pkgs=grabfile(myfilename, compatlevel, recursive=recursive, remember_source_file=True)
453 if not pkgs:
454 return pkgs
455 if verify_eapi and eapi is None:
456 eapi = read_corresponding_eapi_file(myfilename)
457 mybasename = os.path.basename(myfilename)
458 atoms = []
459 for pkg, source_file in pkgs:
460 pkg_orig = pkg
461
462 if pkg[:1] == "-":
463 pkg = pkg[1:]
464 if pkg[:1] == '*' and mybasename == 'packages':
465 pkg = pkg[1:]
466 try:
467 pkg = Atom(pkg, allow_wildcard=allow_wildcard, allow_repo=allow_repo, eapi=eapi)
468 except InvalidAtom as e:
469 writemsg(_("--- Invalid atom in %s: %s\n") % (source_file, e),
470 noiselevel=-1)
471 else:
472 if pkg_orig == _unicode(pkg):
473
474 if remember_source_file:
475 atoms.append((pkg, source_file))
476 else:
477 atoms.append(pkg)
478 else:
479
480 if remember_source_file:
481 atoms.append((pkg_orig, source_file))
482 else:
483 atoms.append(pkg_orig)
484 return atoms
485
487 return not f.startswith(".") and not f.endswith("~")
488
489 -def grablines(myfilename, recursive=0, remember_source_file=False):
490 mylines=[]
491 if recursive and os.path.isdir(myfilename):
492 if os.path.basename(myfilename) in VCS_DIRS:
493 return mylines
494 try:
495 dirlist = os.listdir(myfilename)
496 except OSError as e:
497 if e.errno == PermissionDenied.errno:
498 raise PermissionDenied(myfilename)
499 elif e.errno in (errno.ENOENT, errno.ESTALE):
500 return mylines
501 else:
502 raise
503 dirlist.sort()
504 for f in dirlist:
505 if _recursive_basename_filter(f):
506 mylines.extend(grablines(
507 os.path.join(myfilename, f), recursive, remember_source_file))
508 else:
509 try:
510 myfile = io.open(_unicode_encode(myfilename,
511 encoding=_encodings['fs'], errors='strict'),
512 mode='r', encoding=_encodings['content'], errors='replace')
513 if remember_source_file:
514 mylines = [(line, myfilename) for line in myfile.readlines()]
515 else:
516 mylines = myfile.readlines()
517 myfile.close()
518 except IOError as e:
519 if e.errno == PermissionDenied.errno:
520 raise PermissionDenied(myfilename)
521 elif e.errno in (errno.ENOENT, errno.ESTALE):
522 pass
523 else:
524 raise
525 return mylines
526
527 -def writedict(mydict,myfilename,writekey=True):
528 """Writes out a dict to a file; writekey=0 mode doesn't write out
529 the key and assumes all values are strings, not lists."""
530 lines = []
531 if not writekey:
532 for v in mydict.values():
533 lines.append(v + "\n")
534 else:
535 for k, v in mydict.items():
536 lines.append("%s %s\n" % (k, " ".join(v)))
537 write_atomic(myfilename, "".join(lines))
538
540 """
541 This is equivalent to shlex.split, but if the current interpreter is
542 python2, it temporarily encodes unicode strings to bytes since python2's
543 shlex.split() doesn't handle unicode strings.
544 """
545 convert_to_bytes = sys.hexversion < 0x3000000 and not isinstance(s, bytes)
546 if convert_to_bytes:
547 s = _unicode_encode(s)
548 rval = shlex.split(s)
549 if convert_to_bytes:
550 rval = [_unicode_decode(x) for x in rval]
551 return rval
552
555 try:
556 return shlex.shlex.sourcehook(self, newfile)
557 except EnvironmentError as e:
558 writemsg(_("!!! Parse error in '%s': source command failed: %s\n") % \
559 (self.infile, str(e)), noiselevel=-1)
560 return (newfile, io.StringIO())
561
562 _invalid_var_name_re = re.compile(r'^\d|\W')
563
564 -def getconfig(mycfg, tolerant=False, allow_sourcing=False, expand=True,
565 recursive=False):
566
567 is_dir = False
568 if recursive:
569 try:
570 is_dir = stat.S_ISDIR(os.stat(mycfg).st_mode)
571 except OSError as e:
572 if e.errno == PermissionDenied.errno:
573 raise PermissionDenied(mycfg)
574 elif e.errno in (errno.ENOENT, errno.ESTALE, errno.EISDIR):
575 return None
576 else:
577 raise
578
579 if isinstance(expand, dict):
580
581
582 expand_map = expand
583 expand = True
584 else:
585 expand_map = {}
586 mykeys = {}
587
588 if recursive and is_dir:
589
590
591 def onerror(e):
592 if e.errno == PermissionDenied.errno:
593 raise PermissionDenied(mycfg)
594
595 recursive_files = []
596 for parent, dirs, files in os.walk(mycfg, onerror=onerror):
597 try:
598 parent = _unicode_decode(parent,
599 encoding=_encodings['fs'], errors='strict')
600 except UnicodeDecodeError:
601 continue
602 for fname_enc in dirs[:]:
603 try:
604 fname = _unicode_decode(fname_enc,
605 encoding=_encodings['fs'], errors='strict')
606 except UnicodeDecodeError:
607 dirs.remove(fname_enc)
608 continue
609 if fname in VCS_DIRS or not _recursive_basename_filter(fname):
610 dirs.remove(fname_enc)
611 for fname in files:
612 try:
613 fname = _unicode_decode(fname,
614 encoding=_encodings['fs'], errors='strict')
615 except UnicodeDecodeError:
616 pass
617 else:
618 if _recursive_basename_filter(fname):
619 fname = os.path.join(parent, fname)
620 if os.path.isfile(fname):
621 recursive_files.append(fname)
622 recursive_files.sort()
623 if not expand:
624 expand_map = False
625 for fname in recursive_files:
626 mykeys.update(getconfig(fname, tolerant=tolerant,
627 allow_sourcing=allow_sourcing, expand=expand_map,
628 recursive=False) or {})
629 return mykeys
630
631 f = None
632 try:
633
634
635 if sys.hexversion < 0x3000000:
636 f = open(_unicode_encode(mycfg,
637 encoding=_encodings['fs'], errors='strict'), 'rb')
638 else:
639 f = open(_unicode_encode(mycfg,
640 encoding=_encodings['fs'], errors='strict'), mode='r',
641 encoding=_encodings['content'], errors='replace')
642 content = f.read()
643 except IOError as e:
644 if e.errno == PermissionDenied.errno:
645 raise PermissionDenied(mycfg)
646 if e.errno != errno.ENOENT:
647 writemsg("open('%s', 'r'): %s\n" % (mycfg, e), noiselevel=-1)
648 if e.errno not in (errno.EISDIR,):
649 raise
650 return None
651 finally:
652 if f is not None:
653 f.close()
654
655
656
657
658 if content and content[-1] != '\n':
659 content += '\n'
660
661
662
663 if '\r' in content:
664 writemsg(("!!! " + _("Please use dos2unix to convert line endings " + \
665 "in config file: '%s'") + "\n") % mycfg, noiselevel=-1)
666
667 lex = None
668 try:
669 if tolerant:
670 shlex_class = _tolerant_shlex
671 else:
672 shlex_class = shlex.shlex
673
674
675
676 lex = shlex_class(content, infile=mycfg, posix=True)
677 lex.wordchars = string.digits + string.ascii_letters + \
678 "~!@#$%*_\:;?,./-+{}"
679 lex.quotes="\"'"
680 if allow_sourcing:
681 lex.source="source"
682 while 1:
683 key=lex.get_token()
684 if key == "export":
685 key = lex.get_token()
686 if key is None:
687
688 break;
689 equ=lex.get_token()
690 if (equ==''):
691 msg = lex.error_leader() + _("Unexpected EOF")
692 if not tolerant:
693 raise ParseError(msg)
694 else:
695 writemsg("%s\n" % msg, noiselevel=-1)
696 return mykeys
697 elif (equ!='='):
698 msg = lex.error_leader() + \
699 _("Invalid token '%s' (not '=')") % (equ,)
700 if not tolerant:
701 raise ParseError(msg)
702 else:
703 writemsg("%s\n" % msg, noiselevel=-1)
704 return mykeys
705 val=lex.get_token()
706 if val is None:
707 msg = lex.error_leader() + \
708 _("Unexpected end of config file: variable '%s'") % (key,)
709 if not tolerant:
710 raise ParseError(msg)
711 else:
712 writemsg("%s\n" % msg, noiselevel=-1)
713 return mykeys
714 key = _unicode_decode(key)
715 val = _unicode_decode(val)
716
717 if _invalid_var_name_re.search(key) is not None:
718 msg = lex.error_leader() + \
719 _("Invalid variable name '%s'") % (key,)
720 if not tolerant:
721 raise ParseError(msg)
722 writemsg("%s\n" % msg, noiselevel=-1)
723 continue
724
725 if expand:
726 mykeys[key] = varexpand(val, mydict=expand_map,
727 error_leader=lex.error_leader)
728 expand_map[key] = mykeys[key]
729 else:
730 mykeys[key] = val
731 except SystemExit as e:
732 raise
733 except Exception as e:
734 if isinstance(e, ParseError) or lex is None:
735 raise
736 msg = _unicode_decode("%s%s") % (lex.error_leader(), e)
737 writemsg("%s\n" % msg, noiselevel=-1)
738 raise
739
740 return mykeys
741
742 _varexpand_word_chars = frozenset(string.ascii_letters + string.digits + "_")
743 _varexpand_unexpected_eof_msg = "unexpected EOF while looking for matching `}'"
744
745 -def varexpand(mystring, mydict=None, error_leader=None):
746 if mydict is None:
747 mydict = {}
748
749 """
750 new variable expansion code. Preserves quotes, handles \n, etc.
751 This code is used by the configfile code, as well as others (parser)
752 This would be a good bunch of code to port to C.
753 """
754 numvars=0
755
756 insing=0
757 indoub=0
758 pos = 0
759 length = len(mystring)
760 newstring = []
761 while pos < length:
762 current = mystring[pos]
763 if current == "'":
764 if (indoub):
765 newstring.append("'")
766 else:
767 newstring.append("'")
768 insing=not insing
769 pos=pos+1
770 continue
771 elif current == '"':
772 if (insing):
773 newstring.append('"')
774 else:
775 newstring.append('"')
776 indoub=not indoub
777 pos=pos+1
778 continue
779 if (not insing):
780
781 if current == "\n":
782
783 newstring.append(" ")
784 pos += 1
785 elif current == "\\":
786
787
788
789
790
791
792
793
794 if (pos+1>=len(mystring)):
795 newstring.append(current)
796 break
797 else:
798 current = mystring[pos + 1]
799 pos += 2
800 if current == "$":
801 newstring.append(current)
802 elif current == "\\":
803 newstring.append(current)
804
805
806 if pos < length and \
807 mystring[pos] in ("'", '"', "$"):
808 newstring.append(mystring[pos])
809 pos += 1
810 elif current == "\n":
811 pass
812 else:
813 newstring.append(mystring[pos - 2:pos])
814 continue
815 elif current == "$":
816 pos=pos+1
817 if mystring[pos]=="{":
818 pos=pos+1
819 braced=True
820 else:
821 braced=False
822 myvstart=pos
823 while mystring[pos] in _varexpand_word_chars:
824 if (pos+1)>=len(mystring):
825 if braced:
826 msg = _varexpand_unexpected_eof_msg
827 if error_leader is not None:
828 msg = error_leader() + msg
829 writemsg(msg + "\n", noiselevel=-1)
830 return ""
831 else:
832 pos=pos+1
833 break
834 pos=pos+1
835 myvarname=mystring[myvstart:pos]
836 if braced:
837 if mystring[pos]!="}":
838 msg = _varexpand_unexpected_eof_msg
839 if error_leader is not None:
840 msg = error_leader() + msg
841 writemsg(msg + "\n", noiselevel=-1)
842 return ""
843 else:
844 pos=pos+1
845 if len(myvarname)==0:
846 msg = "$"
847 if braced:
848 msg += "{}"
849 msg += ": bad substitution"
850 if error_leader is not None:
851 msg = error_leader() + msg
852 writemsg(msg + "\n", noiselevel=-1)
853 return ""
854 numvars=numvars+1
855 if myvarname in mydict:
856 newstring.append(mydict[myvarname])
857 else:
858 newstring.append(current)
859 pos += 1
860 else:
861 newstring.append(current)
862 pos += 1
863
864 return "".join(newstring)
865
866
867 pickle_write = None
868
870 if not os.access(filename, os.R_OK):
871 writemsg(_("pickle_read(): File not readable. '")+filename+"'\n",1)
872 return default
873 data = None
874 try:
875 myf = open(_unicode_encode(filename,
876 encoding=_encodings['fs'], errors='strict'), 'rb')
877 mypickle = pickle.Unpickler(myf)
878 data = mypickle.load()
879 myf.close()
880 del mypickle,myf
881 writemsg(_("pickle_read(): Loaded pickle. '")+filename+"'\n",1)
882 except SystemExit as e:
883 raise
884 except Exception as e:
885 writemsg(_("!!! Failed to load pickle: ")+str(e)+"\n",1)
886 data = default
887 return data
888
890 info = sys.exc_info()
891 if not info[2]:
892 stack = traceback.extract_stack()[:-1]
893 error = None
894 else:
895 stack = traceback.extract_tb(info[2])
896 error = str(info[1])
897 writemsg("\n====================================\n", noiselevel=noiselevel)
898 writemsg("%s\n\n" % msg, noiselevel=noiselevel)
899 for line in traceback.format_list(stack):
900 writemsg(line, noiselevel=noiselevel)
901 if error:
902 writemsg(error+"\n", noiselevel=noiselevel)
903 writemsg("====================================\n\n", noiselevel=noiselevel)
904
906 """
907 In python-3.0 the list.sort() method no longer has a "cmp" keyword
908 argument. This class acts as an adapter which converts a cmp function
909 into one that's suitable for use as the "key" keyword argument to
910 list.sort(), making it easier to port code for python-3.0 compatibility.
911 It works by generating key objects which use the given cmp function to
912 implement their __lt__ method.
913
914 Beginning with Python 2.7 and 3.2, equivalent functionality is provided
915 by functools.cmp_to_key().
916 """
917 __slots__ = ("_cmp_func",)
918
920 """
921 @type cmp_func: callable which takes 2 positional arguments
922 @param cmp_func: A cmp function.
923 """
924 self._cmp_func = cmp_func
925
928
930 __slots__ = ("_cmp_func", "_obj")
931
935
937 if other.__class__ is not self.__class__:
938 raise TypeError("Expected type %s, got %s" % \
939 (self.__class__, other.__class__))
940 return self._cmp_func(self._obj, other._obj) < 0
941
943 """lifted from python cookbook, credit: Tim Peters
944 Return a list of the elements in s in arbitrary order, sans duplicates"""
945 n = len(s)
946
947 try:
948 return list(set(s))
949 except TypeError:
950 pass
951
952
953 try:
954 t = list(s)
955 t.sort()
956 except TypeError:
957 pass
958 else:
959 assert n > 0
960 last = t[0]
961 lasti = i = 1
962 while i < n:
963 if t[i] != last:
964 t[lasti] = last = t[i]
965 lasti += 1
966 i += 1
967 return t[:lasti]
968
969
970 u = []
971 for x in s:
972 if x not in u:
973 u.append(x)
974 return u
975
977 """
978 List unique elements, preserving order. Remember all elements ever seen.
979 Taken from itertools documentation.
980 """
981
982
983 seen = set()
984 seen_add = seen.add
985 if key is None:
986 for element in filterfalse(seen.__contains__, iterable):
987 seen_add(element)
988 yield element
989 else:
990 for element in iterable:
991 k = key(element)
992 if k not in seen:
993 seen_add(k)
994 yield element
995
996 -def apply_permissions(filename, uid=-1, gid=-1, mode=-1, mask=-1,
997 stat_cached=None, follow_links=True):
998 """Apply user, group, and mode bits to a file if the existing bits do not
999 already match. The default behavior is to force an exact match of mode
1000 bits. When mask=0 is specified, mode bits on the target file are allowed
1001 to be a superset of the mode argument (via logical OR). When mask>0, the
1002 mode bits that the target file is allowed to have are restricted via
1003 logical XOR.
1004 Returns True if the permissions were modified and False otherwise."""
1005
1006 modified = False
1007
1008 if stat_cached is None:
1009 try:
1010 if follow_links:
1011 stat_cached = os.stat(filename)
1012 else:
1013 stat_cached = os.lstat(filename)
1014 except OSError as oe:
1015 func_call = "stat('%s')" % filename
1016 if oe.errno == errno.EPERM:
1017 raise OperationNotPermitted(func_call)
1018 elif oe.errno == errno.EACCES:
1019 raise PermissionDenied(func_call)
1020 elif oe.errno == errno.ENOENT:
1021 raise FileNotFound(filename)
1022 else:
1023 raise
1024
1025 if (uid != -1 and uid != stat_cached.st_uid) or \
1026 (gid != -1 and gid != stat_cached.st_gid):
1027 try:
1028 if follow_links:
1029 os.chown(filename, uid, gid)
1030 else:
1031 portage.data.lchown(filename, uid, gid)
1032 modified = True
1033 except OSError as oe:
1034 func_call = "chown('%s', %i, %i)" % (filename, uid, gid)
1035 if oe.errno == errno.EPERM:
1036 raise OperationNotPermitted(func_call)
1037 elif oe.errno == errno.EACCES:
1038 raise PermissionDenied(func_call)
1039 elif oe.errno == errno.EROFS:
1040 raise ReadOnlyFileSystem(func_call)
1041 elif oe.errno == errno.ENOENT:
1042 raise FileNotFound(filename)
1043 else:
1044 raise
1045
1046 new_mode = -1
1047 st_mode = stat_cached.st_mode & 0o7777
1048 if mask >= 0:
1049 if mode == -1:
1050 mode = 0
1051 else:
1052 mode = mode & 0o7777
1053 if (mode & st_mode != mode) or \
1054 ((mask ^ st_mode) & st_mode != st_mode):
1055 new_mode = mode | st_mode
1056 new_mode = (mask ^ new_mode) & new_mode
1057 elif mode != -1:
1058 mode = mode & 0o7777
1059 if mode != st_mode:
1060 new_mode = mode
1061
1062
1063
1064 if modified and new_mode == -1 and \
1065 (st_mode & stat.S_ISUID or st_mode & stat.S_ISGID):
1066 if mode == -1:
1067 new_mode = st_mode
1068 else:
1069 mode = mode & 0o7777
1070 if mask >= 0:
1071 new_mode = mode | st_mode
1072 new_mode = (mask ^ new_mode) & new_mode
1073 else:
1074 new_mode = mode
1075 if not (new_mode & stat.S_ISUID or new_mode & stat.S_ISGID):
1076 new_mode = -1
1077
1078 if not follow_links and stat.S_ISLNK(stat_cached.st_mode):
1079
1080 new_mode = -1
1081
1082 if new_mode != -1:
1083 try:
1084 os.chmod(filename, new_mode)
1085 modified = True
1086 except OSError as oe:
1087 func_call = "chmod('%s', %s)" % (filename, oct(new_mode))
1088 if oe.errno == errno.EPERM:
1089 raise OperationNotPermitted(func_call)
1090 elif oe.errno == errno.EACCES:
1091 raise PermissionDenied(func_call)
1092 elif oe.errno == errno.EROFS:
1093 raise ReadOnlyFileSystem(func_call)
1094 elif oe.errno == errno.ENOENT:
1095 raise FileNotFound(filename)
1096 raise
1097 return modified
1098
1100 """A wrapper around apply_secpass_permissions that gets
1101 uid, gid, and mode from a stat object"""
1102 return apply_secpass_permissions(filename, uid=newstat.st_uid, gid=newstat.st_gid,
1103 mode=newstat.st_mode, **kwargs)
1104
1107 """A wrapper around apply_secpass_permissions that applies permissions
1108 recursively. If optional argument onerror is specified, it should be a
1109 function; it will be called with one argument, a PortageException instance.
1110 Returns True if all permissions are applied and False if some are left
1111 unapplied."""
1112
1113
1114 follow_links = False
1115
1116 if onerror is None:
1117
1118
1119 def onerror(e):
1120 if isinstance(e, OperationNotPermitted):
1121 writemsg(_("Operation Not Permitted: %s\n") % str(e),
1122 noiselevel=-1)
1123 elif isinstance(e, FileNotFound):
1124 writemsg(_("File Not Found: '%s'\n") % str(e), noiselevel=-1)
1125 else:
1126 raise
1127
1128 all_applied = True
1129 for dirpath, dirnames, filenames in os.walk(top):
1130 try:
1131 applied = apply_secpass_permissions(dirpath,
1132 uid=uid, gid=gid, mode=dirmode, mask=dirmask,
1133 follow_links=follow_links)
1134 if not applied:
1135 all_applied = False
1136 except PortageException as e:
1137 all_applied = False
1138 onerror(e)
1139
1140 for name in filenames:
1141 try:
1142 applied = apply_secpass_permissions(os.path.join(dirpath, name),
1143 uid=uid, gid=gid, mode=filemode, mask=filemask,
1144 follow_links=follow_links)
1145 if not applied:
1146 all_applied = False
1147 except PortageException as e:
1148
1149
1150
1151 if not isinstance(e, portage.exception.InvalidLocation):
1152 all_applied = False
1153 onerror(e)
1154 return all_applied
1155
1158 """A wrapper around apply_permissions that uses secpass and simple
1159 logic to apply as much of the permissions as possible without
1160 generating an obviously avoidable permission exception. Despite
1161 attempts to avoid an exception, it's possible that one will be raised
1162 anyway, so be prepared.
1163 Returns True if all permissions are applied and False if some are left
1164 unapplied."""
1165
1166 if stat_cached is None:
1167 try:
1168 if follow_links:
1169 stat_cached = os.stat(filename)
1170 else:
1171 stat_cached = os.lstat(filename)
1172 except OSError as oe:
1173 func_call = "stat('%s')" % filename
1174 if oe.errno == errno.EPERM:
1175 raise OperationNotPermitted(func_call)
1176 elif oe.errno == errno.EACCES:
1177 raise PermissionDenied(func_call)
1178 elif oe.errno == errno.ENOENT:
1179 raise FileNotFound(filename)
1180 else:
1181 raise
1182
1183 all_applied = True
1184
1185 if portage.data.secpass < 2:
1186
1187 if uid != -1 and \
1188 uid != stat_cached.st_uid:
1189 all_applied = False
1190 uid = -1
1191
1192 if gid != -1 and \
1193 gid != stat_cached.st_gid and \
1194 gid not in os.getgroups():
1195 all_applied = False
1196 gid = -1
1197
1198 apply_permissions(filename, uid=uid, gid=gid, mode=mode, mask=mask,
1199 stat_cached=stat_cached, follow_links=follow_links)
1200 return all_applied
1201
1203 """Write a file atomically via os.rename(). Atomic replacement prevents
1204 interprocess interference and prevents corruption of the target
1205 file when the write is interrupted (for example, when an 'out of space'
1206 error occurs)."""
1207
1208 - def __init__(self, filename, mode='w', follow_links=True, **kargs):
1209 """Opens a temporary filename.pid in the same directory as filename."""
1210 ObjectProxy.__init__(self)
1211 object.__setattr__(self, '_aborted', False)
1212 if 'b' in mode:
1213 open_func = open
1214 else:
1215 open_func = io.open
1216 kargs.setdefault('encoding', _encodings['content'])
1217 kargs.setdefault('errors', 'backslashreplace')
1218
1219 if follow_links:
1220 canonical_path = os.path.realpath(filename)
1221 object.__setattr__(self, '_real_name', canonical_path)
1222 tmp_name = "%s.%i" % (canonical_path, os.getpid())
1223 try:
1224 object.__setattr__(self, '_file',
1225 open_func(_unicode_encode(tmp_name,
1226 encoding=_encodings['fs'], errors='strict'),
1227 mode=mode, **kargs))
1228 return
1229 except IOError as e:
1230 if canonical_path == filename:
1231 raise
1232
1233
1234
1235
1236 object.__setattr__(self, '_real_name', filename)
1237 tmp_name = "%s.%i" % (filename, os.getpid())
1238 object.__setattr__(self, '_file',
1239 open_func(_unicode_encode(tmp_name,
1240 encoding=_encodings['fs'], errors='strict'),
1241 mode=mode, **kargs))
1242
1245
1246 if sys.hexversion >= 0x3000000:
1247
1249 if attr in ('close', 'abort', '__del__'):
1250 return object.__getattribute__(self, attr)
1251 return getattr(object.__getattribute__(self, '_file'), attr)
1252
1253 else:
1254
1255
1256
1257
1258
1260 if attr in ('close', 'abort', 'write', '__del__'):
1261 return object.__getattribute__(self, attr)
1262 return getattr(object.__getattribute__(self, '_file'), attr)
1263
1269
1271 """Closes the temporary file, copies permissions (if possible),
1272 and performs the atomic replacement via os.rename(). If the abort()
1273 method has been called, then the temp file is closed and removed."""
1274 f = object.__getattribute__(self, '_file')
1275 real_name = object.__getattribute__(self, '_real_name')
1276 if not f.closed:
1277 try:
1278 f.close()
1279 if not object.__getattribute__(self, '_aborted'):
1280 try:
1281 apply_stat_permissions(f.name, os.stat(real_name))
1282 except OperationNotPermitted:
1283 pass
1284 except FileNotFound:
1285 pass
1286 except OSError as oe:
1287 if oe.errno in (errno.ENOENT, errno.EPERM):
1288 pass
1289 else:
1290 raise
1291 os.rename(f.name, real_name)
1292 finally:
1293
1294
1295 try:
1296 os.unlink(f.name)
1297 except OSError as oe:
1298 pass
1299
1301 """If an error occurs while writing the file, the user should
1302 call this method in order to leave the target file unchanged.
1303 This will call close() automatically."""
1304 if not object.__getattribute__(self, '_aborted'):
1305 object.__setattr__(self, '_aborted', True)
1306 self.close()
1307
1309 """If the user does not explicitly call close(), it is
1310 assumed that an error has occurred, so we abort()."""
1311 try:
1312 f = object.__getattribute__(self, '_file')
1313 except AttributeError:
1314 pass
1315 else:
1316 if not f.closed:
1317 self.abort()
1318
1319 base_destructor = getattr(ObjectProxy, '__del__', None)
1320 if base_destructor is not None:
1321 base_destructor(self)
1322
1343
1345 """Create a directory and call apply_permissions.
1346 Returns True if a directory is created or the permissions needed to be
1347 modified, and False otherwise.
1348
1349 This function's handling of EEXIST errors makes it useful for atomic
1350 directory creation, in which multiple processes may be competing to
1351 create the same directory.
1352 """
1353
1354 created_dir = False
1355
1356 try:
1357 os.makedirs(dir_path)
1358 created_dir = True
1359 except OSError as oe:
1360 func_call = "makedirs('%s')" % dir_path
1361 if oe.errno in (errno.EEXIST,):
1362 pass
1363 else:
1364 if os.path.isdir(dir_path):
1365
1366
1367
1368
1369 pass
1370 elif oe.errno == errno.EPERM:
1371 raise OperationNotPermitted(func_call)
1372 elif oe.errno == errno.EACCES:
1373 raise PermissionDenied(func_call)
1374 elif oe.errno == errno.EROFS:
1375 raise ReadOnlyFileSystem(func_call)
1376 else:
1377 raise
1378 if kwargs:
1379 perms_modified = apply_permissions(dir_path, **kwargs)
1380 else:
1381 perms_modified = False
1382 return created_dir or perms_modified
1383
1385 """A mapping object that behaves like a standard dict except that it allows
1386 for lazy initialization of values via callable objects. Lazy items can be
1387 overwritten and deleted just as normal items."""
1388
1389 __slots__ = ('lazy_items',)
1390
1395
1396 - def addLazyItem(self, item_key, value_callable, *pargs, **kwargs):
1397 """Add a lazy item for the given key. When the item is requested,
1398 value_callable will be called with *pargs and **kwargs arguments."""
1399 self.lazy_items[item_key] = \
1400 self._LazyItem(value_callable, pargs, kwargs, False)
1401
1402 UserDict.__setitem__(self, item_key, None)
1403
1405 """This is like addLazyItem except value_callable will only be called
1406 a maximum of 1 time and the result will be cached for future requests."""
1407 self.lazy_items[item_key] = \
1408 self._LazyItem(value_callable, pargs, kwargs, True)
1409
1410 UserDict.__setitem__(self, item_key, None)
1411
1412 - def update(self, *args, **kwargs):
1434
1451
1456
1461
1465
1468
1470 return self.__class__(self)
1471
1473 """
1474 This forces evaluation of each contained lazy item, and deepcopy of
1475 the result. A TypeError is raised if any contained lazy item is not
1476 a singleton, since it is not necessarily possible for the behavior
1477 of this type of item to be safely preserved.
1478 """
1479 if memo is None:
1480 memo = {}
1481 result = self.__class__()
1482 memo[id(self)] = result
1483 for k in self:
1484 k_copy = deepcopy(k, memo)
1485 lazy_item = self.lazy_items.get(k)
1486 if lazy_item is not None:
1487 if not lazy_item.singleton:
1488 raise TypeError(_unicode_decode("LazyItemsDict " + \
1489 "deepcopy is unsafe with lazy items that are " + \
1490 "not singletons: key=%s value=%s") % (k, lazy_item,))
1491 UserDict.__setitem__(result, k_copy, deepcopy(self[k], memo))
1492 return result
1493
1495
1496 __slots__ = ('func', 'pargs', 'kwargs', 'singleton')
1497
1498 - def __init__(self, func, pargs, kwargs, singleton):
1509
1513
1515 """
1516 Override this since the default implementation can fail silently,
1517 leaving some attributes unset.
1518 """
1519 if memo is None:
1520 memo = {}
1521 result = self.__copy__()
1522 memo[id(self)] = result
1523 result.func = deepcopy(self.func, memo)
1524 result.pargs = deepcopy(self.pargs, memo)
1525 result.kwargs = deepcopy(self.kwargs, memo)
1526 result.singleton = deepcopy(self.singleton, memo)
1527 return result
1528
1530 - def __init__(self, myroot, protect_list, mask_list):
1531 self.myroot = myroot
1532 self.protect_list = protect_list
1533 self.mask_list = mask_list
1534 self.updateprotect()
1535
1537 """Update internal state for isprotected() calls. Nonexistent paths
1538 are ignored."""
1539
1540 os = _os_merge
1541
1542 self.protect = []
1543 self._dirs = set()
1544 for x in self.protect_list:
1545 ppath = normalize_path(
1546 os.path.join(self.myroot, x.lstrip(os.path.sep)))
1547 try:
1548 if stat.S_ISDIR(os.stat(ppath).st_mode):
1549 self._dirs.add(ppath)
1550 self.protect.append(ppath)
1551 except OSError:
1552
1553 pass
1554
1555 self.protectmask = []
1556 for x in self.mask_list:
1557 ppath = normalize_path(
1558 os.path.join(self.myroot, x.lstrip(os.path.sep)))
1559 try:
1560 """Use lstat so that anything, even a broken symlink can be
1561 protected."""
1562 if stat.S_ISDIR(os.lstat(ppath).st_mode):
1563 self._dirs.add(ppath)
1564 self.protectmask.append(ppath)
1565 """Now use stat in case this is a symlink to a directory."""
1566 if stat.S_ISDIR(os.stat(ppath).st_mode):
1567 self._dirs.add(ppath)
1568 except OSError:
1569
1570 pass
1571
1573 """Returns True if obj is protected, False otherwise. The caller must
1574 ensure that obj is normalized with a single leading slash. A trailing
1575 slash is optional for directories."""
1576 masked = 0
1577 protected = 0
1578 sep = os.path.sep
1579 for ppath in self.protect:
1580 if len(ppath) > masked and obj.startswith(ppath):
1581 if ppath in self._dirs:
1582 if obj != ppath and not obj.startswith(ppath + sep):
1583
1584 continue
1585 elif obj != ppath:
1586
1587
1588 continue
1589 protected = len(ppath)
1590
1591 for pmpath in self.protectmask:
1592 if len(pmpath) >= protected and obj.startswith(pmpath):
1593 if pmpath in self._dirs:
1594 if obj != pmpath and \
1595 not obj.startswith(pmpath + sep):
1596
1597 continue
1598 elif obj != pmpath:
1599
1600
1601 continue
1602
1603 masked = len(pmpath)
1604 return protected > masked
1605
1607 """Resolves a config-protect filename for merging, optionally
1608 using the last filename if the md5 matches. If force is True,
1609 then a new filename will be generated even if mydest does not
1610 exist yet.
1611 (dest,md5) ==> 'string' --- path_to_target_filename
1612 (dest) ==> ('next', 'highest') --- next_target and most-recent_target
1613 """
1614
1615
1616
1617
1618
1619 os = _os_merge
1620
1621 prot_num = -1
1622 last_pfile = ""
1623
1624 if not force and \
1625 not os.path.exists(mydest):
1626 return mydest
1627
1628 real_filename = os.path.basename(mydest)
1629 real_dirname = os.path.dirname(mydest)
1630 for pfile in os.listdir(real_dirname):
1631 if pfile[0:5] != "._cfg":
1632 continue
1633 if pfile[10:] != real_filename:
1634 continue
1635 try:
1636 new_prot_num = int(pfile[5:9])
1637 if new_prot_num > prot_num:
1638 prot_num = new_prot_num
1639 last_pfile = pfile
1640 except ValueError:
1641 continue
1642 prot_num = prot_num + 1
1643
1644 new_pfile = normalize_path(os.path.join(real_dirname,
1645 "._cfg" + str(prot_num).zfill(4) + "_" + real_filename))
1646 old_pfile = normalize_path(os.path.join(real_dirname, last_pfile))
1647 if last_pfile and newmd5:
1648 try:
1649 last_pfile_md5 = portage.checksum._perform_md5_merge(old_pfile)
1650 except FileNotFound:
1651
1652 pass
1653 else:
1654 if last_pfile_md5 == newmd5:
1655 return old_pfile
1656 return new_pfile
1657
1659 """
1660 Return a tuple of configuration files that needs to be updated.
1661 The tuple contains lists organized like this:
1662 [ protected_dir, file_list ]
1663 If the protected config isn't a protected_dir but a procted_file, list is:
1664 [ protected_file, None ]
1665 If no configuration files needs to be updated, None is returned
1666 """
1667
1668 encoding = _encodings['fs']
1669
1670 if config_protect:
1671
1672 for x in config_protect:
1673 files = []
1674
1675 x = os.path.join(target_root, x.lstrip(os.path.sep))
1676 if not os.access(x, os.W_OK):
1677 continue
1678 try:
1679 mymode = os.lstat(x).st_mode
1680 except OSError:
1681 continue
1682
1683 if stat.S_ISLNK(mymode):
1684
1685
1686 try:
1687 real_mode = os.stat(x).st_mode
1688 if stat.S_ISDIR(real_mode):
1689 mymode = real_mode
1690 except OSError:
1691 pass
1692
1693 if stat.S_ISDIR(mymode):
1694 mycommand = \
1695 "find '%s' -name '.*' -type d -prune -o -name '._cfg????_*'" % x
1696 else:
1697 mycommand = "find '%s' -maxdepth 1 -name '._cfg????_%s'" % \
1698 os.path.split(x.rstrip(os.path.sep))
1699 mycommand += " ! -name '.*~' ! -iname '.*.bak' -print0"
1700 cmd = shlex_split(mycommand)
1701 if sys.hexversion < 0x3000000 or sys.hexversion >= 0x3020000:
1702
1703 cmd = [_unicode_encode(arg, encoding=encoding, errors='strict')
1704 for arg in cmd]
1705 proc = subprocess.Popen(cmd, stdout=subprocess.PIPE,
1706 stderr=subprocess.STDOUT)
1707 output = _unicode_decode(proc.communicate()[0], encoding=encoding)
1708 status = proc.wait()
1709 if os.WIFEXITED(status) and os.WEXITSTATUS(status) == os.EX_OK:
1710 files = output.split('\0')
1711
1712 if files and not files[-1]:
1713 del files[-1]
1714 if files:
1715 if stat.S_ISDIR(mymode):
1716 yield (x, files)
1717 else:
1718 yield (x, None)
1719
1720 _ld_so_include_re = re.compile(r'^include\s+(\S.*)')
1721
1723 def read_ld_so_conf(path):
1724 for l in grabfile(path):
1725 include_match = _ld_so_include_re.match(l)
1726 if include_match is not None:
1727 subpath = os.path.join(os.path.dirname(path),
1728 include_match.group(1))
1729 for p in glob.glob(subpath):
1730 for r in read_ld_so_conf(p):
1731 yield r
1732 else:
1733 yield l
1734
1735 """ Return a list of paths that are used for library lookups """
1736 if env is None:
1737 env = os.environ
1738
1739 rval = env.get("LD_LIBRARY_PATH", "").split(":")
1740 rval.extend(read_ld_so_conf(os.path.join(root, "etc", "ld.so.conf")))
1741 rval.append("/usr/lib")
1742 rval.append("/lib")
1743
1744 return [normalize_path(x) for x in rval if x]
1745