elf.py revision 1933
1516N/A#!/usr/bin/python
1231N/A#
1231N/A# CDDL HEADER START
1231N/A#
1231N/A# The contents of this file are subject to the terms of the
1231N/A# Common Development and Distribution License (the "License").
1231N/A# You may not use this file except in compliance with the License.
1231N/A#
1231N/A# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
1231N/A# or http://www.opensolaris.org/os/licensing.
1231N/A# See the License for the specific language governing permissions
1231N/A# and limitations under the License.
1231N/A#
1231N/A# When distributing Covered Code, include this CDDL HEADER in each
1231N/A# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
1231N/A# If applicable, add the following below this CDDL HEADER, with the
1231N/A# fields enclosed by brackets "[]" replaced with your own identifying
1231N/A# information: Portions Copyright [yyyy] [name of copyright owner]
1231N/A#
1231N/A# CDDL HEADER END
1231N/A#
1231N/A
1231N/A#
1908N/A# Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved.
1231N/A#
1231N/A
1231N/Aimport os
1231N/A
1231N/Aimport pkg.elf as elf
1231N/Aimport pkg.flavor.base as base
1231N/A
1908N/Afrom pkg.portable import PD_LOCAL_PATH, PD_PROTO_DIR
1500N/A
1231N/Aclass BadElfFile(base.DependencyAnalysisError):
1231N/A """Exception that is raised when the elf dependency checker is given
1231N/A a file that errors when it tries to get the dynamic section from the
1231N/A file."""
1231N/A
1231N/A def __init__(self, fp, ex):
1231N/A base.DependencyAnalysisError.__init__(self)
1231N/A self.fp = fp
1231N/A self.ex = ex
1231N/A
1231N/A def __str__(self):
1231N/A return _("%s had this elf error:%s") % (self.fp, self.ex)
1231N/A
1231N/Aclass UnsupportedDynamicToken(base.DependencyAnalysisError):
1231N/A """Exception that is used for elf dependencies which have a dynamic
1231N/A token in their path that we're unable to decode."""
1231N/A
1544N/A def __init__(self, proto_path, installed_path, run_path, token):
1231N/A base.DependencyAnalysisError.__init__(self)
1544N/A self.pp = proto_path
1544N/A self.ip = installed_path
1231N/A self.rp = run_path
1231N/A self.tok = token
1231N/A
1231N/A def __str__(self):
1600N/A return _("%(pp)s (which will be installed at %(ip)s) had this "
1600N/A "token, %(tok)s, in its run path: %(rp)s. It is not "
1600N/A "currently possible to automatically expand this token. "
1600N/A "Please specify its value on the command line.") % \
1600N/A self.__dict__
1231N/A
1231N/A
1580N/Aclass ElfDependency(base.PublishingDependency):
1231N/A """Class representing a dependency from one file to another library
1231N/A as determined by elf."""
1231N/A
1580N/A def __init__(self, action, base_name, run_paths, pkg_vars, proto_dir):
1231N/A self.err_type = self.ERROR
1231N/A
1580N/A base.PublishingDependency.__init__(self, action,
1580N/A [base_name], run_paths, pkg_vars, proto_dir, "elf")
1231N/A
1231N/A def is_error(self):
1580N/A """Because elf dependencies can be either warnings or errors,
1580N/A it's necessary to check whether this dependency is an error
1580N/A or not."""
1580N/A
1231N/A return self.err_type == self.ERROR
1231N/A
1231N/A def resolve_internal(self, delivered_base_names, **kwargs):
1231N/A """Checks whether this dependency has been delivered. If the
1231N/A full path has not been delivered, check whether the base name
1231N/A has. If it has, it's likely that the run path is being set
1231N/A externally. Report a warning, but not an error in this case."""
1580N/A err, vars = base.PublishingDependency.resolve_internal(
1580N/A self, delivered_base_names=delivered_base_names, **kwargs)
1231N/A # If the none of the paths pointed to a file with the desired
1231N/A # basename, but a file with that basename was delivered by this
1231N/A # package, then treat the dependency as a warning instead of
1231N/A # an error. The failure to find the path to the right file
1231N/A # may be due to the library search path being set outside the
1231N/A # file that generates the dependency.
1580N/A if err == self.ERROR and vars.is_satisfied() and \
1580N/A self.base_names[0] in delivered_base_names:
1231N/A self.err_type = self.WARNING
1231N/A self.attrs["%s.severity" % self.DEPEND_DEBUG_PREFIX] =\
1231N/A "warning"
1231N/A return self.WARNING, self.get_var_diff(
1580N/A delivered_base_names[self.base_names[0]])
1231N/A else:
1231N/A return err, vars
1231N/A
1231N/A def __repr__(self):
1715N/A return "ElfDep(%s, %s, %s, %s)" % (self.action,
1715N/A self.base_names[0], self.run_paths, self.pkg_vars)
1231N/A
1544N/Adef expand_variables(paths, dyn_tok_conv):
1544N/A """Replace dynamic tokens, such as $PLATFORM, in the paths in the
1544N/A paramter 'paths' with the values for that token provided in the
1544N/A dictionary 'dyn_tok_conv.'
1544N/A """
1544N/A
1544N/A res = []
1544N/A elist = []
1544N/A for p in paths:
1544N/A tok_start = p.find("$")
1544N/A if tok_start > -1:
1544N/A tok = p[tok_start:]
1544N/A tok_end = tok.find("/")
1544N/A if tok_end > -1:
1544N/A tok = tok[:tok_end]
1544N/A if tok not in dyn_tok_conv:
1544N/A elist.append((p, tok))
1544N/A else:
1544N/A np = [
1544N/A p[:tok_start] + dc + \
1544N/A p[tok_start + len(tok):]
1544N/A for dc in dyn_tok_conv[tok]
1544N/A ]
1544N/A # The first dynamic token has been replaced, but
1544N/A # more may remain so process the path again.
1544N/A rec_res, rec_elist = expand_variables(np,
1544N/A dyn_tok_conv)
1544N/A res.extend(rec_res)
1544N/A elist.extend(rec_elist)
1544N/A else:
1544N/A res.append(p)
1544N/A return res, elist
1580N/A
1580N/Adefault_run_paths = ["/lib", "/usr/lib"]
1580N/A
1908N/Adef process_elf_dependencies(action, pkg_vars, dyn_tok_conv,
1544N/A kernel_paths, **kwargs):
1544N/A """Produce the elf dependencies for the file delivered in the action
1544N/A provided.
1544N/A
1544N/A 'action' is the file action to analyze.
1544N/A
1544N/A 'pkg_vars' is the list of variants against which the package delivering
1544N/A the action was published.
1544N/A
1544N/A 'dyn_tok_conv' is the dictionary which maps the dynamic tokens, like
1544N/A $PLATFORM, to the values they should be expanded to.
1544N/A
1544N/A 'kernel_paths' contains the run paths which kernel modules should use.
1544N/A """
1231N/A
1231N/A if not action.name == "file":
1933N/A return [], [], {}
1231N/A
1231N/A installed_path = action.attrs[action.key_attr]
1544N/A
1500N/A proto_file = action.attrs[PD_LOCAL_PATH]
1231N/A
1231N/A if not os.path.exists(proto_file):
1231N/A raise base.MissingFile(proto_file)
1231N/A
1231N/A if not elf.is_elf_object(proto_file):
1933N/A return [], [], {}
1231N/A
1231N/A try:
1231N/A ei = elf.get_info(proto_file)
1231N/A ed = elf.get_dynamic(proto_file)
1231N/A except elf.ElfError, e:
1231N/A raise BadElfFile(proto_file, e)
1231N/A deps = [
1231N/A d[0]
1231N/A for d in ed.get("deps", [])
1231N/A ]
1231N/A rp = ed.get("runpath", "").split(":")
1231N/A if len(rp) == 1 and rp[0] == "":
1231N/A rp = []
1231N/A
1544N/A dyn_tok_conv["$ORIGIN"] = [os.path.join("/",
1544N/A os.path.dirname(installed_path))]
1231N/A
1231N/A kernel64 = None
1231N/A
1231N/A # For kernel modules, default path resolution is /platform/<platform>,
1231N/A # /kernel, /usr/kernel. But how do we know what <platform> would be for
1231N/A # a given module? Does it do fallbacks to, say, sun4u?
1231N/A if installed_path.startswith("kernel") or \
1231N/A installed_path.startswith("usr/kernel") or \
1231N/A (installed_path.startswith("platform") and \
1231N/A installed_path.split("/")[2] == "kernel"):
1231N/A if rp:
1231N/A raise RuntimeError("RUNPATH set for kernel module "
1293N/A "(%s): %s" % (installed_path, rp))
1231N/A # Add this platform to the search path.
1293N/A if installed_path.startswith("platform"):
1293N/A rp.append("/platform/%s/kernel" %
1582N/A installed_path.split("/")[1])
1582N/A else:
1582N/A for p in dyn_tok_conv.get("$PLATFORM", []):
1582N/A rp.append("/platform/%s/kernel" % p)
1231N/A # Default kernel search path
1544N/A rp.extend(kernel_paths)
1231N/A # What subdirectory should we look in for 64-bit kernel modules?
1231N/A if ei["bits"] == 64:
1231N/A if ei["arch"] == "i386":
1231N/A kernel64 = "amd64"
1231N/A elif ei["arch"] == "sparc":
1231N/A kernel64 = "sparcv9"
1231N/A else:
1231N/A raise RuntimeError("Unknown arch:%s" %
1231N/A ei["arch"])
1231N/A else:
1580N/A for p in default_run_paths:
1580N/A if p not in rp:
1580N/A rp.append(p)
1231N/A
1544N/A rp, elist = expand_variables(rp, dyn_tok_conv)
1544N/A
1544N/A elist = [
1544N/A UnsupportedDynamicToken(proto_file, installed_path, p, tok)
1544N/A for p, tok in elist
1544N/A ]
1544N/A
1231N/A res = []
1231N/A
1544N/A for d in deps:
1544N/A pn, fn = os.path.split(d)
1544N/A pathlist = []
1544N/A for p in rp:
1544N/A if kernel64:
1544N/A # Find 64-bit modules the way krtld does.
1544N/A # XXX We don't resolve dependencies found in
1544N/A # /platform, since we don't know where under
1544N/A # /platform to look.
1544N/A deppath = os.path.join(p, pn, kernel64, fn)[1:]
1544N/A else:
1544N/A # This is a hack for when a runpath uses the 64
1544N/A # symlink to the actual 64-bit directory.
1544N/A # Better would be to see if the runpath was a
1544N/A # link, and if so, use its resolution, but
1544N/A # extracting that information from used list is
1544N/A # a pain, especially because you potentially
1544N/A # have to resolve symlinks at all levels of the
1544N/A # path.
1544N/A if p.endswith("/64"):
1544N/A if ei["arch"] == "i386":
1544N/A p = p[:-2] + "amd64"
1544N/A elif ei["arch"] == "sparc":
1544N/A p = p[:-2] + "sparcv9"
1544N/A deppath = os.path.join(p, d).lstrip(os.path.sep)
1544N/A # deppath includes filename; remove that.
1544N/A head, tail = os.path.split(deppath)
1544N/A if head:
1544N/A pathlist.append(head)
1544N/A res.append(ElfDependency(action, fn, pathlist, pkg_vars,
1908N/A action.attrs[PD_PROTO_DIR]))
1544N/A del dyn_tok_conv["$ORIGIN"]
1933N/A return res, elist, {}