| # Copyright 2010-2014 Gentoo Foundation |
| # Distributed under the terms of the GNU General Public License v2 |
| |
| __all__ = ['env_update'] |
| |
| import errno |
| import glob |
| import io |
| import stat |
| import sys |
| import time |
| |
| import portage |
| from portage import os, _encodings, _unicode_decode, _unicode_encode |
| from portage.checksum import prelink_capable |
| from portage.data import ostype |
| from portage.exception import ParseError |
| from portage.localization import _ |
| from portage.process import find_binary |
| from portage.util import atomic_ofstream, ensure_dirs, getconfig, \ |
| normalize_path, writemsg |
| from portage.util.listdir import listdir |
| from portage.dbapi.vartree import vartree |
| from portage.package.ebuild.config import config |
| |
| if sys.hexversion >= 0x3000000: |
| # pylint: disable=W0622 |
| long = int |
| |
| def env_update(makelinks=1, target_root=None, prev_mtimes=None, contents=None, |
| env=None, writemsg_level=None, vardbapi=None): |
| """ |
| Parse /etc/env.d and use it to generate /etc/profile.env, csh.env, |
| ld.so.conf, and prelink.conf. Finally, run ldconfig. When ldconfig is |
| called, its -X option will be used in order to avoid potential |
| interference with installed soname symlinks that are required for |
| correct operation of FEATURES=preserve-libs for downgrade operations. |
| It's not necessary for ldconfig to create soname symlinks, since |
| portage will use NEEDED.ELF.2 data to automatically create them |
| after src_install if they happen to be missing. |
| @param makelinks: True if ldconfig should be called, False otherwise |
| @param target_root: root that is passed to the ldconfig -r option, |
| defaults to portage.settings["ROOT"]. |
| @type target_root: String (Path) |
| """ |
| settings = getattr(portage, 'settings', None) |
| if settings is None: |
| settings = config(config_root=target_root, |
| target_root=target_root) |
| |
| if 'no-env-update' in settings.features: |
| return |
| |
| if vardbapi is None: |
| if isinstance(env, config): |
| vardbapi = vartree(settings=env).dbapi |
| else: |
| if target_root is None: |
| eprefix = portage.settings["EPREFIX"] |
| target_root = portage.settings["ROOT"] |
| target_eroot = portage.settings['EROOT'] |
| else: |
| eprefix = portage.const.EPREFIX |
| target_eroot = os.path.join(target_root, |
| eprefix.lstrip(os.sep)) |
| target_eroot = target_eroot.rstrip(os.sep) + os.sep |
| if hasattr(portage, "db") and target_eroot in portage.db: |
| vardbapi = portage.db[target_eroot]["vartree"].dbapi |
| else: |
| settings = config(config_root=target_root, |
| target_root=target_root, eprefix=eprefix) |
| target_root = settings["ROOT"] |
| if env is None: |
| env = settings |
| vardbapi = vartree(settings=settings).dbapi |
| |
| # Lock the config memory file to prevent symlink creation |
| # in merge_contents from overlapping with env-update. |
| vardbapi._fs_lock() |
| try: |
| return _env_update(makelinks, target_root, prev_mtimes, contents, |
| env, writemsg_level) |
| finally: |
| vardbapi._fs_unlock() |
| |
| def _env_update(makelinks, target_root, prev_mtimes, contents, env, |
| writemsg_level): |
| if writemsg_level is None: |
| writemsg_level = portage.util.writemsg_level |
| if target_root is None: |
| target_root = portage.settings["ROOT"] |
| if prev_mtimes is None: |
| prev_mtimes = portage.mtimedb["ldpath"] |
| if env is None: |
| settings = portage.settings |
| else: |
| settings = env |
| |
| eprefix = settings.get("EPREFIX", "") |
| eprefix_lstrip = eprefix.lstrip(os.sep) |
| eroot = normalize_path(os.path.join(target_root, eprefix_lstrip)).rstrip(os.sep) + os.sep |
| envd_dir = os.path.join(eroot, "etc", "env.d") |
| ensure_dirs(envd_dir, mode=0o755) |
| fns = listdir(envd_dir, EmptyOnError=1) |
| fns.sort() |
| templist = [] |
| for x in fns: |
| if len(x) < 3: |
| continue |
| if not x[0].isdigit() or not x[1].isdigit(): |
| continue |
| if x.startswith(".") or x.endswith("~") or x.endswith(".bak"): |
| continue |
| templist.append(x) |
| fns = templist |
| del templist |
| |
| space_separated = set(["CONFIG_PROTECT", "CONFIG_PROTECT_MASK"]) |
| colon_separated = set(["ADA_INCLUDE_PATH", "ADA_OBJECTS_PATH", |
| "CLASSPATH", "INFODIR", "INFOPATH", "KDEDIRS", "LDPATH", "MANPATH", |
| "PATH", "PKG_CONFIG_PATH", "PRELINK_PATH", "PRELINK_PATH_MASK", |
| "PYTHONPATH", "ROOTPATH"]) |
| |
| config_list = [] |
| |
| for x in fns: |
| file_path = os.path.join(envd_dir, x) |
| try: |
| myconfig = getconfig(file_path, expand=False) |
| except ParseError as e: |
| writemsg("!!! '%s'\n" % str(e), noiselevel=-1) |
| del e |
| continue |
| if myconfig is None: |
| # broken symlink or file removed by a concurrent process |
| writemsg("!!! File Not Found: '%s'\n" % file_path, noiselevel=-1) |
| continue |
| |
| config_list.append(myconfig) |
| if "SPACE_SEPARATED" in myconfig: |
| space_separated.update(myconfig["SPACE_SEPARATED"].split()) |
| del myconfig["SPACE_SEPARATED"] |
| if "COLON_SEPARATED" in myconfig: |
| colon_separated.update(myconfig["COLON_SEPARATED"].split()) |
| del myconfig["COLON_SEPARATED"] |
| |
| env = {} |
| specials = {} |
| for var in space_separated: |
| mylist = [] |
| for myconfig in config_list: |
| if var in myconfig: |
| for item in myconfig[var].split(): |
| if item and not item in mylist: |
| mylist.append(item) |
| del myconfig[var] # prepare for env.update(myconfig) |
| if mylist: |
| env[var] = " ".join(mylist) |
| specials[var] = mylist |
| |
| for var in colon_separated: |
| mylist = [] |
| for myconfig in config_list: |
| if var in myconfig: |
| for item in myconfig[var].split(":"): |
| if item and not item in mylist: |
| mylist.append(item) |
| del myconfig[var] # prepare for env.update(myconfig) |
| if mylist: |
| env[var] = ":".join(mylist) |
| specials[var] = mylist |
| |
| for myconfig in config_list: |
| """Cumulative variables have already been deleted from myconfig so that |
| they won't be overwritten by this dict.update call.""" |
| env.update(myconfig) |
| |
| ldsoconf_path = os.path.join(eroot, "etc", "ld.so.conf") |
| try: |
| myld = io.open(_unicode_encode(ldsoconf_path, |
| encoding=_encodings['fs'], errors='strict'), |
| mode='r', encoding=_encodings['content'], errors='replace') |
| myldlines = myld.readlines() |
| myld.close() |
| oldld = [] |
| for x in myldlines: |
| #each line has at least one char (a newline) |
| if x[:1] == "#": |
| continue |
| oldld.append(x[:-1]) |
| except (IOError, OSError) as e: |
| if e.errno != errno.ENOENT: |
| raise |
| oldld = None |
| |
| newld = specials["LDPATH"] |
| if (oldld != newld): |
| #ld.so.conf needs updating and ldconfig needs to be run |
| myfd = atomic_ofstream(ldsoconf_path) |
| myfd.write("# ld.so.conf autogenerated by env-update; make all changes to\n") |
| myfd.write("# contents of /etc/env.d directory\n") |
| for x in specials["LDPATH"]: |
| myfd.write(x + "\n") |
| myfd.close() |
| |
| potential_lib_dirs = set() |
| for lib_dir_glob in ('usr/lib*', 'lib*'): |
| x = os.path.join(eroot, lib_dir_glob) |
| for y in glob.glob(_unicode_encode(x, |
| encoding=_encodings['fs'], errors='strict')): |
| try: |
| y = _unicode_decode(y, |
| encoding=_encodings['fs'], errors='strict') |
| except UnicodeDecodeError: |
| continue |
| if os.path.basename(y) != 'libexec': |
| potential_lib_dirs.add(y[len(eroot):]) |
| |
| # Update prelink.conf if we are prelink-enabled |
| if prelink_capable: |
| prelink_d = os.path.join(eroot, 'etc', 'prelink.conf.d') |
| ensure_dirs(prelink_d) |
| newprelink = atomic_ofstream(os.path.join(prelink_d, 'portage.conf')) |
| newprelink.write("# prelink.conf autogenerated by env-update; make all changes to\n") |
| newprelink.write("# contents of /etc/env.d directory\n") |
| |
| for x in sorted(potential_lib_dirs) + ['bin', 'sbin']: |
| newprelink.write('-l /%s\n' % (x,)); |
| prelink_paths = set() |
| prelink_paths |= set(specials.get('LDPATH', [])) |
| prelink_paths |= set(specials.get('PATH', [])) |
| prelink_paths |= set(specials.get('PRELINK_PATH', [])) |
| prelink_path_mask = specials.get('PRELINK_PATH_MASK', []) |
| for x in prelink_paths: |
| if not x: |
| continue |
| if x[-1:] != '/': |
| x += "/" |
| plmasked = 0 |
| for y in prelink_path_mask: |
| if not y: |
| continue |
| if y[-1] != '/': |
| y += "/" |
| if y == x[0:len(y)]: |
| plmasked = 1 |
| break |
| if not plmasked: |
| newprelink.write("-h %s\n" % (x,)) |
| for x in prelink_path_mask: |
| newprelink.write("-b %s\n" % (x,)) |
| newprelink.close() |
| |
| # Migration code path. If /etc/prelink.conf was generated by us, then |
| # point it to the new stuff until the prelink package re-installs. |
| prelink_conf = os.path.join(eroot, 'etc', 'prelink.conf') |
| try: |
| with open(_unicode_encode(prelink_conf, |
| encoding=_encodings['fs'], errors='strict'), 'rb') as f: |
| if f.readline() == b'# prelink.conf autogenerated by env-update; make all changes to\n': |
| f = atomic_ofstream(prelink_conf) |
| f.write('-c /etc/prelink.conf.d/*.conf\n') |
| f.close() |
| except IOError as e: |
| if e.errno != errno.ENOENT: |
| raise |
| |
| current_time = long(time.time()) |
| mtime_changed = False |
| |
| lib_dirs = set() |
| for lib_dir in set(specials['LDPATH']) | potential_lib_dirs: |
| x = os.path.join(eroot, lib_dir.lstrip(os.sep)) |
| try: |
| newldpathtime = os.stat(x)[stat.ST_MTIME] |
| lib_dirs.add(normalize_path(x)) |
| except OSError as oe: |
| if oe.errno == errno.ENOENT: |
| try: |
| del prev_mtimes[x] |
| except KeyError: |
| pass |
| # ignore this path because it doesn't exist |
| continue |
| raise |
| if newldpathtime == current_time: |
| # Reset mtime to avoid the potential ambiguity of times that |
| # differ by less than 1 second. |
| newldpathtime -= 1 |
| os.utime(x, (newldpathtime, newldpathtime)) |
| prev_mtimes[x] = newldpathtime |
| mtime_changed = True |
| elif x in prev_mtimes: |
| if prev_mtimes[x] == newldpathtime: |
| pass |
| else: |
| prev_mtimes[x] = newldpathtime |
| mtime_changed = True |
| else: |
| prev_mtimes[x] = newldpathtime |
| mtime_changed = True |
| |
| if makelinks and \ |
| not mtime_changed and \ |
| contents is not None: |
| libdir_contents_changed = False |
| for mypath, mydata in contents.items(): |
| if mydata[0] not in ("obj", "sym"): |
| continue |
| head, tail = os.path.split(mypath) |
| if head in lib_dirs: |
| libdir_contents_changed = True |
| break |
| if not libdir_contents_changed: |
| makelinks = False |
| |
| if "CHOST" in settings and "CBUILD" in settings and \ |
| settings["CHOST"] != settings["CBUILD"]: |
| ldconfig = find_binary("%s-ldconfig" % settings["CHOST"]) |
| else: |
| ldconfig = os.path.join(eroot, "sbin", "ldconfig") |
| |
| if ldconfig is None: |
| pass |
| elif not (os.access(ldconfig, os.X_OK) and os.path.isfile(ldconfig)): |
| ldconfig = None |
| |
| # Only run ldconfig as needed |
| if makelinks and ldconfig: |
| # ldconfig has very different behaviour between FreeBSD and Linux |
| if ostype == "Linux" or ostype.lower().endswith("gnu"): |
| # We can't update links if we haven't cleaned other versions first, as |
| # an older package installed ON TOP of a newer version will cause ldconfig |
| # to overwrite the symlinks we just made. -X means no links. After 'clean' |
| # we can safely create links. |
| writemsg_level(_(">>> Regenerating %setc/ld.so.cache...\n") % \ |
| (target_root,)) |
| os.system("cd / ; %s -X -r '%s'" % (ldconfig, target_root)) |
| elif ostype in ("FreeBSD", "DragonFly"): |
| writemsg_level(_(">>> Regenerating %svar/run/ld-elf.so.hints...\n") % \ |
| target_root) |
| os.system(("cd / ; %s -elf -i " + \ |
| "-f '%svar/run/ld-elf.so.hints' '%setc/ld.so.conf'") % \ |
| (ldconfig, target_root, target_root)) |
| |
| del specials["LDPATH"] |
| |
| penvnotice = "# THIS FILE IS AUTOMATICALLY GENERATED BY env-update.\n" |
| penvnotice += "# DO NOT EDIT THIS FILE. CHANGES TO STARTUP PROFILES\n" |
| cenvnotice = penvnotice[:] |
| penvnotice += "# GO INTO /etc/profile NOT /etc/profile.env\n\n" |
| cenvnotice += "# GO INTO /etc/csh.cshrc NOT /etc/csh.env\n\n" |
| |
| #create /etc/profile.env for bash support |
| outfile = atomic_ofstream(os.path.join(eroot, "etc", "profile.env")) |
| outfile.write(penvnotice) |
| |
| env_keys = [x for x in env if x != "LDPATH"] |
| env_keys.sort() |
| for k in env_keys: |
| v = env[k] |
| if v.startswith('$') and not v.startswith('${'): |
| outfile.write("export %s=$'%s'\n" % (k, v[1:])) |
| else: |
| outfile.write("export %s='%s'\n" % (k, v)) |
| outfile.close() |
| |
| #create /etc/csh.env for (t)csh support |
| outfile = atomic_ofstream(os.path.join(eroot, "etc", "csh.env")) |
| outfile.write(cenvnotice) |
| for x in env_keys: |
| outfile.write("setenv %s '%s'\n" % (x, env[x])) |
| outfile.close() |