kernel modules no longer live in $ISADIR
[unleashed-pkg5.git] / src / modules / flavor / elf.py
blobc936824795f4f9f99f0f4b499fd6b7a835867004
1 #!/usr/bin/python2.7
3 # CDDL HEADER START
5 # The contents of this file are subject to the terms of the
6 # Common Development and Distribution License (the "License").
7 # You may not use this file except in compliance with the License.
9 # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10 # or http://www.opensolaris.org/os/licensing.
11 # See the License for the specific language governing permissions
12 # and limitations under the License.
14 # When distributing Covered Code, include this CDDL HEADER in each
15 # file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16 # If applicable, add the following below this CDDL HEADER, with the
17 # fields enclosed by brackets "[]" replaced with your own identifying
18 # information: Portions Copyright [yyyy] [name of copyright owner]
20 # CDDL HEADER END
24 # Copyright (c) 2009, 2015, Oracle and/or its affiliates. All rights reserved.
27 import os
29 import pkg.elf as elf
30 import pkg.flavor.base as base
32 from pkg.portable import PD_LOCAL_PATH, PD_PROTO_DIR, PD_DEFAULT_RUNPATH
34 class BadElfFile(base.DependencyAnalysisError):
35 """Exception that is raised when the elf dependency checker is given
36 a file that errors when it tries to get the dynamic section from the
37 file."""
39 def __init__(self, fp, ex):
40 base.DependencyAnalysisError.__init__(self)
41 self.fp = fp
42 self.ex = ex
44 def __str__(self):
45 return _("{file} had this elf error:{err}").format(
46 file="self.fp", err=self.ex)
48 class UnsupportedDynamicToken(base.DependencyAnalysisError):
49 """Exception that is used for elf dependencies which have a dynamic
50 token in their path that we're unable to decode."""
52 def __init__(self, proto_path, installed_path, run_path, token):
53 base.DependencyAnalysisError.__init__(self)
54 self.pp = proto_path
55 self.ip = installed_path
56 self.rp = run_path
57 self.tok = token
59 def __str__(self):
60 return _("{pp} (which will be installed at {ip}) had this "
61 "token, {tok}, in its run path: {rp}. It is not "
62 "currently possible to automatically expand this token. "
63 "Please specify its value on the command line.").format(
64 **self.__dict__)
67 class ElfDependency(base.PublishingDependency):
68 """Class representing a dependency from one file to another library
69 as determined by elf."""
71 def __init__(self, action, base_name, run_paths, pkg_vars, proto_dir):
72 self.err_type = self.ERROR
74 base.PublishingDependency.__init__(self, action,
75 [base_name], run_paths, pkg_vars, proto_dir, "elf")
77 def is_error(self):
78 """Because elf dependencies can be either warnings or errors,
79 it's necessary to check whether this dependency is an error
80 or not."""
82 return self.err_type == self.ERROR
84 def resolve_internal(self, delivered_base_names, **kwargs):
85 """Checks whether this dependency has been delivered. If the
86 full path has not been delivered, check whether the base name
87 has. If it has, it's likely that the run path is being set
88 externally. Report a warning, but not an error in this case."""
89 err, vars = base.PublishingDependency.resolve_internal(
90 self, delivered_base_names=delivered_base_names, **kwargs)
91 # If the none of the paths pointed to a file with the desired
92 # basename, but a file with that basename was delivered by this
93 # package, then treat the dependency as a warning instead of
94 # an error. The failure to find the path to the right file
95 # may be due to the library search path being set outside the
96 # file that generates the dependency.
97 if err == self.ERROR and vars.is_satisfied() and \
98 self.base_names[0] in delivered_base_names:
99 self.err_type = self.WARNING
100 self.attrs["{0}.severity".format(self.DEPEND_DEBUG_PREFIX)] =\
101 "warning"
102 missing_vars = self.get_variant_combinations()
103 missing_vars.mark_as_satisfied(
104 delivered_base_names[self.base_names[0]])
105 return self.WARNING, missing_vars
106 else:
107 return err, vars
109 def __repr__(self):
110 return "ElfDep({0}, {1}, {2}, {3})".format(self.action,
111 self.base_names[0], self.run_paths, self.pkg_vars)
113 def expand_variables(paths, dyn_tok_conv):
114 """Replace dynamic tokens, such as $PLATFORM, in the paths in the
115 paramter 'paths' with the values for that token provided in the
116 dictionary 'dyn_tok_conv.'
119 res = []
120 elist = []
121 for p in paths:
122 tok_start = p.find("$")
123 if tok_start > -1:
124 tok = p[tok_start:]
125 tok_end = tok.find("/")
126 if tok_end > -1:
127 tok = tok[:tok_end]
128 if tok not in dyn_tok_conv:
129 elist.append((p, tok))
130 else:
131 np = [
132 p[:tok_start] + dc + \
133 p[tok_start + len(tok):]
134 for dc in dyn_tok_conv[tok]
136 # The first dynamic token has been replaced, but
137 # more may remain so process the path again.
138 rec_res, rec_elist = expand_variables(np,
139 dyn_tok_conv)
140 res.extend(rec_res)
141 elist.extend(rec_elist)
142 else:
143 res.append(p)
144 return res, elist
146 default_run_paths = ["/lib", "/usr/lib"]
148 def process_elf_dependencies(action, pkg_vars, dyn_tok_conv, run_paths,
149 **kwargs):
150 """Produce the elf dependencies for the file delivered in the action
151 provided.
153 'action' is the file action to analyze.
155 'pkg_vars' is the list of variants against which the package delivering
156 the action was published.
158 'dyn_tok_conv' is the dictionary which maps the dynamic tokens, like
159 $PLATFORM, to the values they should be expanded to.
161 'run_paths' contains the run paths which elf binaries should use.
164 if not action.name == "file":
165 return [], [], {}
167 installed_path = action.attrs[action.key_attr]
169 proto_file = action.attrs[PD_LOCAL_PATH]
171 if not os.path.exists(proto_file):
172 raise base.MissingFile(proto_file)
174 if not elf.is_elf_object(proto_file):
175 return [], [], {}
177 try:
178 ei = elf.get_info(proto_file)
179 ed = elf.get_dynamic(proto_file, sha1=False, sha256=False)
180 except elf.ElfError as e:
181 raise BadElfFile(proto_file, e)
182 deps = [
183 d[0]
184 for d in ed.get("deps", [])
186 rp = ed.get("runpath", "").split(":")
187 if len(rp) == 1 and rp[0] == "":
188 rp = []
190 dyn_tok_conv["$ORIGIN"] = [os.path.join("/",
191 os.path.dirname(installed_path))]
193 # For kernel modules, default path resolution is /platform/<platform>,
194 # /kernel, /usr/kernel. But how do we know what <platform> would be for
195 # a given module? Does it do fallbacks to, say, sun4u?
196 if installed_path.startswith("kernel") or \
197 installed_path.startswith("usr/kernel") or \
198 (installed_path.startswith("platform") and \
199 installed_path.split("/")[2] == "kernel"):
200 if rp:
201 raise RuntimeError("RUNPATH set for kernel module "
202 "({0}): {1}".format(installed_path, rp))
203 # Add this platform to the search path.
204 if installed_path.startswith("platform"):
205 rp.append("/platform/{0}/kernel".format(
206 installed_path.split("/")[1]))
207 else:
208 for p in dyn_tok_conv.get("$PLATFORM", []):
209 rp.append("/platform/{0}/kernel".format(p))
210 # Default kernel search path
211 rp.extend(["/kernel", "/usr/kernel"])
212 else:
213 for p in default_run_paths:
214 if ei["bits"] == 64:
215 p += "/64"
216 if p not in rp:
217 rp.append(p)
219 elist = []
220 if run_paths:
221 # add our detected runpaths into the user-supplied one (if any)
222 rp = base.insert_default_runpath(rp, run_paths)
224 rp, errs = expand_variables(rp, dyn_tok_conv)
226 elist.extend([
227 UnsupportedDynamicToken(proto_file, installed_path, p, tok)
228 for p, tok in errs
231 res = []
233 for d in deps:
234 pn, fn = os.path.split(d)
235 pathlist = []
236 for p in rp:
237 deppath = os.path.join(p, d).lstrip(os.path.sep)
238 # deppath includes filename; remove that.
239 head, tail = os.path.split(deppath)
240 if head:
241 pathlist.append(head)
242 res.append(ElfDependency(action, fn, pathlist, pkg_vars,
243 action.attrs[PD_PROTO_DIR]))
244 del dyn_tok_conv["$ORIGIN"]
245 return res, elist, {}