Package portage :: Package util
[hide private]

Source Code for Package portage.util

   1  # Copyright 2004-2013 Gentoo Foundation 
   2  # Distributed under the terms of the GNU General Public License v2 
   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   
56 -def initialize_logger(level=logging.WARN):
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 # avoid potential UnicodeEncodeError 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
83 -def writemsg_stdout(mystr,noiselevel=0):
84 """Prints messages stdout based on the noiselimit setting""" 85 writemsg(mystr, noiselevel=noiselevel, fd=sys.stdout)
86
87 -def writemsg_level(msg, level=0, noiselevel=0):
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
107 -def normalize_path(mypath):
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 # posixpath.normpath collapses 3 or more leading slashes to just 1. 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 #the split/join thing removes leading and trailing whitespace, and converts any whitespace in the line 133 #into single spaces. 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 # Check if we have a compat-level string. BC-integration data. 149 # '##COMPAT==>N<==' 'some string attached to it' 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 # It's a compat line, and the key matches. 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
167 -def map_dictlist_vals(func,myDict):
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 #Let -cat/pkg remove cat/pkg::repo. 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 # Atom.without_repo instantiates a new Atom, 295 # which is unnecessary here, so use string 296 # replacement instead. 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 #the split/join thing removes leading and trailing whitespace, and converts any whitespace in the line 364 #into single spaces. 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
390 -def read_corresponding_eapi_file(filename, default="0"):
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 # We need to call keys() here in order to avoid the possibility of 436 # "RuntimeError: dictionary changed size during iteration" 437 # when an invalid atom is deleted. 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 # for packages and package.mask files 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 # normal atom, so return as Atom instance 474 if remember_source_file: 475 atoms.append((pkg, source_file)) 476 else: 477 atoms.append(pkg) 478 else: 479 # atom has special prefix, so return as string 480 if remember_source_file: 481 atoms.append((pkg_orig, source_file)) 482 else: 483 atoms.append(pkg_orig) 484 return atoms
485
486 -def _recursive_basename_filter(f):
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
539 -def shlex_split(s):
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
553 -class _tolerant_shlex(shlex.shlex):
554 - def sourcehook(self, newfile):
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 # Some existing variable definitions have been 581 # passed in, for use in substitutions. 582 expand_map = expand 583 expand = True 584 else: 585 expand_map = {} 586 mykeys = {} 587 588 if recursive and is_dir: 589 # Emulate source commands so that syntax error messages 590 # can display real file names and line numbers. 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 # NOTE: shlex doesn't support unicode objects with Python 2 634 # (produces spurious \0 characters). 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 # Workaround for avoiding a silent error in shlex that is 656 # triggered by a source statement at the end of the file 657 # without a trailing newline after the source statement. 658 if content and content[-1] != '\n': 659 content += '\n' 660 661 # Warn about dos-style line endings since that prevents 662 # people from being able to source them with bash. 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 # The default shlex.sourcehook() implementation 674 # only joins relative paths when the infile 675 # attribute is properly set. 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 #normal end of file 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 #in single, double quotes 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("'") # Quote removal is handled by shlex. 768 insing=not insing 769 pos=pos+1 770 continue 771 elif current == '"': 772 if (insing): 773 newstring.append('"') 774 else: 775 newstring.append('"') # Quote removal is handled by shlex. 776 indoub=not indoub 777 pos=pos+1 778 continue 779 if (not insing): 780 #expansion time 781 if current == "\n": 782 #convert newlines to spaces 783 newstring.append(" ") 784 pos += 1 785 elif current == "\\": 786 # For backslash expansion, this function used to behave like 787 # echo -e, but that's not needed for our purposes. We want to 788 # behave like bash does when expanding a variable assignment 789 # in a sourced file, in which case it performs backslash 790 # removal for \\ and \$ but nothing more. It also removes 791 # escaped newline characters. Note that we don't handle 792 # escaped quotes here, since getconfig() uses shlex 793 # to handle that earlier. 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 # BUG: This spot appears buggy, but it's intended to 805 # be bug-for-bug compatible with existing behavior. 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 # broken and removed, but can still be imported 867 pickle_write = None 868
869 -def pickle_read(filename,default=None,debug=0):
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
889 -def dump_traceback(msg, noiselevel=1):
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
905 -class cmp_sort_key(object):
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
919 - def __init__(self, cmp_func):
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
926 - def __call__(self, lhs):
927 return self._cmp_key(self._cmp_func, lhs)
928
929 - class _cmp_key(object):
930 __slots__ = ("_cmp_func", "_obj") 931
932 - def __init__(self, cmp_func, obj):
933 self._cmp_func = cmp_func 934 self._obj = obj
935
936 - def __lt__(self, other):
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
942 -def unique_array(s):
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 # assume all elements are hashable, if so, it's linear 947 try: 948 return list(set(s)) 949 except TypeError: 950 pass 951 952 # so much for linear. abuse sort. 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 # blah. back to original portage.unique_array 970 u = [] 971 for x in s: 972 if x not in u: 973 u.append(x) 974 return u
975
976 -def unique_everseen(iterable, key=None):
977 """ 978 List unique elements, preserving order. Remember all elements ever seen. 979 Taken from itertools documentation. 980 """ 981 # unique_everseen('AAAABBBCCDAABBB') --> A B C D 982 # unique_everseen('ABBCcAD', str.lower) --> A B C D 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 # protect from unwanted bits 1048 if mask >= 0: 1049 if mode == -1: 1050 mode = 0 # Don't add any mode bits when mode is unspecified. 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 # protect from unwanted bits 1059 if mode != st_mode: 1060 new_mode = mode 1061 1062 # The chown system call may clear S_ISUID and S_ISGID 1063 # bits, so those bits are restored if necessary. 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 # Mode doesn't matter for symlinks. 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
1099 -def apply_stat_permissions(filename, newstat, **kwargs):
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
1105 -def apply_recursive_permissions(top, uid=-1, gid=-1, 1106 dirmode=-1, dirmask=-1, filemode=-1, filemask=-1, onerror=None):
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 # Avoid issues with circular symbolic links, as in bug #339670. 1114 follow_links = False 1115 1116 if onerror is None: 1117 # Default behavior is to dump errors to stderr so they won't 1118 # go unnoticed. Callers can pass in a quiet instance. 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 # Ignore InvalidLocation exceptions such as FileNotFound 1149 # and DirectoryNotFound since sometimes things disappear, 1150 # like when adjusting permissions on DISTCC_DIR. 1151 if not isinstance(e, portage.exception.InvalidLocation): 1152 all_applied = False 1153 onerror(e) 1154 return all_applied 1155
1156 -def apply_secpass_permissions(filename, uid=-1, gid=-1, mode=-1, mask=-1, 1157 stat_cached=None, follow_links=True):
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
1202 -class atomic_ofstream(ObjectProxy):
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 # Ignore this error, since it's irrelevant 1233 # and the below open call will produce a 1234 # new error if necessary. 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
1243 - def _get_target(self):
1244 return object.__getattribute__(self, '_file')
1245 1246 if sys.hexversion >= 0x3000000: 1247
1248 - def __getattribute__(self, attr):
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 # For TextIOWrapper, automatically coerce write calls to 1256 # unicode, in order to avoid TypeError when writing raw 1257 # bytes with python2. 1258
1259 - def __getattribute__(self, attr):
1260 if attr in ('close', 'abort', 'write', '__del__'): 1261 return object.__getattribute__(self, attr) 1262 return getattr(object.__getattribute__(self, '_file'), attr)
1263
1264 - def write(self, s):
1265 f = object.__getattribute__(self, '_file') 1266 if isinstance(f, io.TextIOWrapper): 1267 s = _unicode_decode(s) 1268 return f.write(s)
1269
1270 - def close(self):
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: # from the above os.stat call 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 # Make sure we cleanup the temp file 1294 # even if an exception is raised. 1295 try: 1296 os.unlink(f.name) 1297 except OSError as oe: 1298 pass
1299
1300 - def abort(self):
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
1308 - def __del__(self):
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 # ensure destructor from the base class is called 1319 base_destructor = getattr(ObjectProxy, '__del__', None) 1320 if base_destructor is not None: 1321 base_destructor(self)
1322
1323 -def write_atomic(file_path, content, **kwargs):
1324 f = None 1325 try: 1326 f = atomic_ofstream(file_path, **kwargs) 1327 f.write(content) 1328 f.close() 1329 except (IOError, OSError) as e: 1330 if f: 1331 f.abort() 1332 func_call = "write_atomic('%s')" % file_path 1333 if e.errno == errno.EPERM: 1334 raise OperationNotPermitted(func_call) 1335 elif e.errno == errno.EACCES: 1336 raise PermissionDenied(func_call) 1337 elif e.errno == errno.EROFS: 1338 raise ReadOnlyFileSystem(func_call) 1339 elif e.errno == errno.ENOENT: 1340 raise FileNotFound(file_path) 1341 else: 1342 raise
1343
1344 -def ensure_dirs(dir_path, **kwargs):
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 # NOTE: DragonFly raises EPERM for makedir('/') 1366 # and that is supposed to be ignored here. 1367 # Also, sometimes mkdir raises EISDIR on FreeBSD 1368 # and we want to ignore that too (bug #187518). 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
1384 -class LazyItemsDict(UserDict):
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
1391 - def __init__(self, *args, **kwargs):
1392 1393 self.lazy_items = {} 1394 UserDict.__init__(self, *args, **kwargs)
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 # make it show up in self.keys(), etc... 1402 UserDict.__setitem__(self, item_key, None)
1403
1404 - def addLazySingleton(self, item_key, value_callable, *pargs, **kwargs):
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 # make it show up in self.keys(), etc... 1410 UserDict.__setitem__(self, item_key, None)
1411
1412 - def update(self, *args, **kwargs):
1413 if len(args) > 1: 1414 raise TypeError( 1415 "expected at most 1 positional argument, got " + \ 1416 repr(len(args))) 1417 if args: 1418 map_obj = args[0] 1419 else: 1420 map_obj = None 1421 if map_obj is None: 1422 pass 1423 elif isinstance(map_obj, LazyItemsDict): 1424 for k in map_obj: 1425 if k in map_obj.lazy_items: 1426 UserDict.__setitem__(self, k, None) 1427 else: 1428 UserDict.__setitem__(self, k, map_obj[k]) 1429 self.lazy_items.update(map_obj.lazy_items) 1430 else: 1431 UserDict.update(self, map_obj) 1432 if kwargs: 1433 UserDict.update(self, kwargs)
1434
1435 - def __getitem__(self, item_key):
1436 if item_key in self.lazy_items: 1437 lazy_item = self.lazy_items[item_key] 1438 pargs = lazy_item.pargs 1439 if pargs is None: 1440 pargs = () 1441 kwargs = lazy_item.kwargs 1442 if kwargs is None: 1443 kwargs = {} 1444 result = lazy_item.func(*pargs, **kwargs) 1445 if lazy_item.singleton: 1446 self[item_key] = result 1447 return result 1448 1449 else: 1450 return UserDict.__getitem__(self, item_key)
1451
1452 - def __setitem__(self, item_key, value):
1453 if item_key in self.lazy_items: 1454 del self.lazy_items[item_key] 1455 UserDict.__setitem__(self, item_key, value)
1456
1457 - def __delitem__(self, item_key):
1458 if item_key in self.lazy_items: 1459 del self.lazy_items[item_key] 1460 UserDict.__delitem__(self, item_key)
1461
1462 - def clear(self):
1463 self.lazy_items.clear() 1464 UserDict.clear(self)
1465
1466 - def copy(self):
1467 return self.__copy__()
1468
1469 - def __copy__(self):
1470 return self.__class__(self)
1471
1472 - def __deepcopy__(self, memo=None):
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
1494 - class _LazyItem(object):
1495 1496 __slots__ = ('func', 'pargs', 'kwargs', 'singleton') 1497
1498 - def __init__(self, func, pargs, kwargs, singleton):
1499 1500 if not pargs: 1501 pargs = None 1502 if not kwargs: 1503 kwargs = None 1504 1505 self.func = func 1506 self.pargs = pargs 1507 self.kwargs = kwargs 1508 self.singleton = singleton
1509
1510 - def __copy__(self):
1511 return self.__class__(self.func, self.pargs, 1512 self.kwargs, self.singleton)
1513
1514 - def __deepcopy__(self, memo=None):
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
1529 -class ConfigProtect(object):
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
1536 - def updateprotect(self):
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 # If it doesn't exist, there's no need to protect it. 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 # If it doesn't exist, there's no need to mask it. 1570 pass
1571
1572 - def isprotected(self, obj):
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 # /etc/foo does not match /etc/foobaz 1584 continue 1585 elif obj != ppath: 1586 # force exact match when CONFIG_PROTECT lists a 1587 # non-directory 1588 continue 1589 protected = len(ppath) 1590 #config file management 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 # /etc/foo does not match /etc/foobaz 1597 continue 1598 elif obj != pmpath: 1599 # force exact match when CONFIG_PROTECT_MASK lists 1600 # a non-directory 1601 continue 1602 #skip, it's in the mask 1603 masked = len(pmpath) 1604 return protected > masked
1605
1606 -def new_protect_filename(mydest, newmd5=None, force=False):
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 # config protection filename format: 1616 # ._cfg0000_foo 1617 # 0123456789012 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 # The file suddenly disappeared or it's a broken symlink. 1652 pass 1653 else: 1654 if last_pfile_md5 == newmd5: 1655 return old_pfile 1656 return new_pfile
1657
1658 -def find_updated_config_files(target_root, config_protect):
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 # directories with some protect files in them 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 # We want to treat it like a directory if it 1685 # is a symlink to an existing directory. 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 # Python 3.1 does not support bytes in Popen args. 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 # split always produces an empty string as the last element 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
1722 -def getlibpaths(root, env=None):
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 # the following is based on the information from ld.so(8) 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