diff options
Diffstat (limited to 'meta/classes/insane.bbclass')
-rw-r--r-- | meta/classes/insane.bbclass | 954 |
1 files changed, 954 insertions, 0 deletions
diff --git a/meta/classes/insane.bbclass b/meta/classes/insane.bbclass new file mode 100644 index 0000000000..a784aff3a9 --- /dev/null +++ b/meta/classes/insane.bbclass | |||
@@ -0,0 +1,954 @@ | |||
1 | # BB Class inspired by ebuild.sh | ||
2 | # | ||
3 | # This class will test files after installation for certain | ||
4 | # security issues and other kind of issues. | ||
5 | # | ||
6 | # Checks we do: | ||
7 | # -Check the ownership and permissions | ||
8 | # -Check the RUNTIME path for the $TMPDIR | ||
9 | # -Check if .la files wrongly point to workdir | ||
10 | # -Check if .pc files wrongly point to workdir | ||
11 | # -Check if packages contains .debug directories or .so files | ||
12 | # where they should be in -dev or -dbg | ||
13 | # -Check if config.log contains traces to broken autoconf tests | ||
14 | # -Ensure that binaries in base_[bindir|sbindir|libdir] do not link | ||
15 | # into exec_prefix | ||
16 | # -Check that scripts in base_[bindir|sbindir|libdir] do not reference | ||
17 | # files under exec_prefix | ||
18 | |||
19 | |||
20 | PACKAGE_DEPENDS += "${QADEPENDS}" | ||
21 | PACKAGEFUNCS += " do_package_qa " | ||
22 | |||
23 | # unsafe-references-in-binaries requires prelink-rtld from | ||
24 | # prelink-native, but we don't want this DEPENDS for -native builds | ||
25 | QADEPENDS = "prelink-native" | ||
26 | QADEPENDS_class-native = "" | ||
27 | QADEPENDS_class-nativesdk = "" | ||
28 | QA_SANE = "True" | ||
29 | |||
30 | # Elect whether a given type of error is a warning or error, they may | ||
31 | # have been set by other files. | ||
32 | WARN_QA ?= "ldflags useless-rpaths rpaths staticdev libdir xorg-driver-abi \ | ||
33 | textrel already-stripped incompatible-license files-invalid \ | ||
34 | installed-vs-shipped compile-host-path install-host-path \ | ||
35 | pn-overrides infodir \ | ||
36 | " | ||
37 | ERROR_QA ?= "dev-so debug-deps dev-deps debug-files arch pkgconfig la \ | ||
38 | perms dep-cmp pkgvarcheck perm-config perm-line perm-link \ | ||
39 | split-strip packages-list pkgv-undefined var-undefined \ | ||
40 | version-going-backwards \ | ||
41 | " | ||
42 | |||
43 | ALL_QA = "${WARN_QA} ${ERROR_QA}" | ||
44 | |||
45 | # | ||
46 | # dictionary for elf headers | ||
47 | # | ||
48 | # feel free to add and correct. | ||
49 | # | ||
50 | # TARGET_OS TARGET_ARCH MACHINE, OSABI, ABIVERSION, Little Endian, 32bit? | ||
51 | def package_qa_get_machine_dict(): | ||
52 | return { | ||
53 | "darwin9" : { | ||
54 | "arm" : (40, 0, 0, True, 32), | ||
55 | }, | ||
56 | "linux" : { | ||
57 | "aarch64" : (183, 0, 0, True, 64), | ||
58 | "arm" : (40, 97, 0, True, 32), | ||
59 | "armeb": (40, 97, 0, False, 32), | ||
60 | "powerpc": (20, 0, 0, False, 32), | ||
61 | "powerpc64": (21, 0, 0, False, 64), | ||
62 | "i386": ( 3, 0, 0, True, 32), | ||
63 | "i486": ( 3, 0, 0, True, 32), | ||
64 | "i586": ( 3, 0, 0, True, 32), | ||
65 | "i686": ( 3, 0, 0, True, 32), | ||
66 | "x86_64": (62, 0, 0, True, 64), | ||
67 | "ia64": (50, 0, 0, True, 64), | ||
68 | "alpha": (36902, 0, 0, True, 64), | ||
69 | "hppa": (15, 3, 0, False, 32), | ||
70 | "m68k": ( 4, 0, 0, False, 32), | ||
71 | "mips": ( 8, 0, 0, False, 32), | ||
72 | "mipsel": ( 8, 0, 0, True, 32), | ||
73 | "mips64": ( 8, 0, 0, False, 64), | ||
74 | "mips64el": ( 8, 0, 0, True, 64), | ||
75 | "s390": (22, 0, 0, False, 32), | ||
76 | "sh4": (42, 0, 0, True, 32), | ||
77 | "sparc": ( 2, 0, 0, False, 32), | ||
78 | "microblaze": (189, 0, 0, False, 32), | ||
79 | "microblazeel":(189, 0, 0, True, 32), | ||
80 | }, | ||
81 | "linux-uclibc" : { | ||
82 | "arm" : ( 40, 97, 0, True, 32), | ||
83 | "armeb": ( 40, 97, 0, False, 32), | ||
84 | "powerpc": ( 20, 0, 0, False, 32), | ||
85 | "i386": ( 3, 0, 0, True, 32), | ||
86 | "i486": ( 3, 0, 0, True, 32), | ||
87 | "i586": ( 3, 0, 0, True, 32), | ||
88 | "i686": ( 3, 0, 0, True, 32), | ||
89 | "x86_64": ( 62, 0, 0, True, 64), | ||
90 | "mips": ( 8, 0, 0, False, 32), | ||
91 | "mipsel": ( 8, 0, 0, True, 32), | ||
92 | "mips64": ( 8, 0, 0, False, 64), | ||
93 | "mips64el": ( 8, 0, 0, True, 64), | ||
94 | "avr32": (6317, 0, 0, False, 32), | ||
95 | "sh4": (42, 0, 0, True, 32), | ||
96 | |||
97 | }, | ||
98 | "uclinux-uclibc" : { | ||
99 | "bfin": ( 106, 0, 0, True, 32), | ||
100 | }, | ||
101 | "linux-gnueabi" : { | ||
102 | "arm" : (40, 0, 0, True, 32), | ||
103 | "armeb" : (40, 0, 0, False, 32), | ||
104 | }, | ||
105 | "linux-uclibceabi" : { | ||
106 | "arm" : (40, 0, 0, True, 32), | ||
107 | "armeb" : (40, 0, 0, False, 32), | ||
108 | }, | ||
109 | "linux-gnuspe" : { | ||
110 | "powerpc": (20, 0, 0, False, 32), | ||
111 | }, | ||
112 | "linux-uclibcspe" : { | ||
113 | "powerpc": (20, 0, 0, False, 32), | ||
114 | }, | ||
115 | "linux-gnu" : { | ||
116 | "powerpc": (20, 0, 0, False, 32), | ||
117 | "sh4": (42, 0, 0, True, 32), | ||
118 | }, | ||
119 | "linux-gnux32" : { | ||
120 | "x86_64": (62, 0, 0, True, 32), | ||
121 | }, | ||
122 | "linux-gnun32" : { | ||
123 | "mips64": ( 8, 0, 0, False, 32), | ||
124 | "mips64el": ( 8, 0, 0, True, 32), | ||
125 | }, | ||
126 | } | ||
127 | |||
128 | |||
129 | def package_qa_clean_path(path,d): | ||
130 | """ Remove the common prefix from the path. In this case it is the TMPDIR""" | ||
131 | return path.replace(d.getVar('TMPDIR',True),"") | ||
132 | |||
133 | def package_qa_write_error(error, d): | ||
134 | logfile = d.getVar('QA_LOGFILE', True) | ||
135 | if logfile: | ||
136 | p = d.getVar('P', True) | ||
137 | f = file( logfile, "a+") | ||
138 | print >> f, "%s: %s" % (p, error) | ||
139 | f.close() | ||
140 | |||
141 | def package_qa_handle_error(error_class, error_msg, d): | ||
142 | package_qa_write_error(error_msg, d) | ||
143 | if error_class in (d.getVar("ERROR_QA", True) or "").split(): | ||
144 | bb.error("QA Issue: %s" % error_msg) | ||
145 | d.setVar("QA_SANE", False) | ||
146 | return False | ||
147 | elif error_class in (d.getVar("WARN_QA", True) or "").split(): | ||
148 | bb.warn("QA Issue: %s" % error_msg) | ||
149 | else: | ||
150 | bb.note("QA Issue: %s" % error_msg) | ||
151 | return True | ||
152 | |||
153 | QAPATHTEST[libexec] = "package_qa_check_libexec" | ||
154 | def package_qa_check_libexec(path,name, d, elf, messages): | ||
155 | |||
156 | # Skip the case where the default is explicitly /usr/libexec | ||
157 | libexec = d.getVar('libexecdir', True) | ||
158 | if libexec == "/usr/libexec": | ||
159 | return True | ||
160 | |||
161 | if 'libexec' in path.split(os.path.sep): | ||
162 | messages.append("%s: %s is using libexec please relocate to %s" % (name, package_qa_clean_path(path, d), libexec)) | ||
163 | return False | ||
164 | |||
165 | return True | ||
166 | |||
167 | QAPATHTEST[rpaths] = "package_qa_check_rpath" | ||
168 | def package_qa_check_rpath(file,name, d, elf, messages): | ||
169 | """ | ||
170 | Check for dangerous RPATHs | ||
171 | """ | ||
172 | if not elf: | ||
173 | return | ||
174 | |||
175 | if os.path.islink(file): | ||
176 | return | ||
177 | |||
178 | bad_dirs = [d.getVar('BASE_WORKDIR', True), d.getVar('STAGING_DIR_TARGET', True)] | ||
179 | |||
180 | phdrs = elf.run_objdump("-p", d) | ||
181 | |||
182 | import re | ||
183 | rpath_re = re.compile("\s+RPATH\s+(.*)") | ||
184 | for line in phdrs.split("\n"): | ||
185 | m = rpath_re.match(line) | ||
186 | if m: | ||
187 | rpath = m.group(1) | ||
188 | for dir in bad_dirs: | ||
189 | if dir in rpath: | ||
190 | messages.append("package %s contains bad RPATH %s in file %s" % (name, rpath, file)) | ||
191 | |||
192 | QAPATHTEST[useless-rpaths] = "package_qa_check_useless_rpaths" | ||
193 | def package_qa_check_useless_rpaths(file, name, d, elf, messages): | ||
194 | """ | ||
195 | Check for RPATHs that are useless but not dangerous | ||
196 | """ | ||
197 | def rpath_eq(a, b): | ||
198 | return os.path.normpath(a) == os.path.normpath(b) | ||
199 | |||
200 | if not elf: | ||
201 | return | ||
202 | |||
203 | if os.path.islink(file): | ||
204 | return | ||
205 | |||
206 | libdir = d.getVar("libdir", True) | ||
207 | base_libdir = d.getVar("base_libdir", True) | ||
208 | |||
209 | phdrs = elf.run_objdump("-p", d) | ||
210 | |||
211 | import re | ||
212 | rpath_re = re.compile("\s+RPATH\s+(.*)") | ||
213 | for line in phdrs.split("\n"): | ||
214 | m = rpath_re.match(line) | ||
215 | if m: | ||
216 | rpath = m.group(1) | ||
217 | if rpath_eq(rpath, libdir) or rpath_eq(rpath, base_libdir): | ||
218 | # The dynamic linker searches both these places anyway. There is no point in | ||
219 | # looking there again. | ||
220 | messages.append("%s: %s contains probably-redundant RPATH %s" % (name, package_qa_clean_path(file, d), rpath)) | ||
221 | |||
222 | QAPATHTEST[dev-so] = "package_qa_check_dev" | ||
223 | def package_qa_check_dev(path, name, d, elf, messages): | ||
224 | """ | ||
225 | Check for ".so" library symlinks in non-dev packages | ||
226 | """ | ||
227 | |||
228 | if not name.endswith("-dev") and not name.endswith("-dbg") and not name.endswith("-ptest") and not name.startswith("nativesdk-") and path.endswith(".so") and os.path.islink(path): | ||
229 | messages.append("non -dev/-dbg/-nativesdk package contains symlink .so: %s path '%s'" % \ | ||
230 | (name, package_qa_clean_path(path,d))) | ||
231 | |||
232 | QAPATHTEST[staticdev] = "package_qa_check_staticdev" | ||
233 | def package_qa_check_staticdev(path, name, d, elf, messages): | ||
234 | """ | ||
235 | Check for ".a" library in non-staticdev packages | ||
236 | There are a number of exceptions to this rule, -pic packages can contain | ||
237 | static libraries, the _nonshared.a belong with their -dev packages and | ||
238 | libgcc.a, libgcov.a will be skipped in their packages | ||
239 | """ | ||
240 | |||
241 | if not name.endswith("-pic") and not name.endswith("-staticdev") and not name.endswith("-ptest") and path.endswith(".a") and not path.endswith("_nonshared.a"): | ||
242 | messages.append("non -staticdev package contains static .a library: %s path '%s'" % \ | ||
243 | (name, package_qa_clean_path(path,d))) | ||
244 | |||
245 | def package_qa_check_libdir(d): | ||
246 | """ | ||
247 | Check for wrong library installation paths. For instance, catch | ||
248 | recipes installing /lib/bar.so when ${base_libdir}="lib32" or | ||
249 | installing in /usr/lib64 when ${libdir}="/usr/lib" | ||
250 | """ | ||
251 | import re | ||
252 | |||
253 | pkgdest = d.getVar('PKGDEST', True) | ||
254 | base_libdir = d.getVar("base_libdir",True) + os.sep | ||
255 | libdir = d.getVar("libdir", True) + os.sep | ||
256 | exec_prefix = d.getVar("exec_prefix", True) + os.sep | ||
257 | |||
258 | messages = [] | ||
259 | |||
260 | lib_re = re.compile("^/lib.+\.so(\..+)?$") | ||
261 | exec_re = re.compile("^%s.*/lib.+\.so(\..+)?$" % exec_prefix) | ||
262 | |||
263 | for root, dirs, files in os.walk(pkgdest): | ||
264 | if root == pkgdest: | ||
265 | # Skip subdirectories for any packages with libdir in INSANE_SKIP | ||
266 | skippackages = [] | ||
267 | for package in dirs: | ||
268 | if 'libdir' in (d.getVar('INSANE_SKIP_' + package, True) or "").split(): | ||
269 | bb.note("Package %s skipping libdir QA test" % (package)) | ||
270 | skippackages.append(package) | ||
271 | for package in skippackages: | ||
272 | dirs.remove(package) | ||
273 | for file in files: | ||
274 | full_path = os.path.join(root, file) | ||
275 | rel_path = os.path.relpath(full_path, pkgdest) | ||
276 | if os.sep in rel_path: | ||
277 | package, rel_path = rel_path.split(os.sep, 1) | ||
278 | rel_path = os.sep + rel_path | ||
279 | if lib_re.match(rel_path): | ||
280 | if base_libdir not in rel_path: | ||
281 | messages.append("%s: found library in wrong location: %s" % (package, rel_path)) | ||
282 | if exec_re.match(rel_path): | ||
283 | if libdir not in rel_path: | ||
284 | messages.append("%s: found library in wrong location: %s" % (package, rel_path)) | ||
285 | |||
286 | if messages: | ||
287 | package_qa_handle_error("libdir", "\n".join(messages), d) | ||
288 | |||
289 | QAPATHTEST[debug-files] = "package_qa_check_dbg" | ||
290 | def package_qa_check_dbg(path, name, d, elf, messages): | ||
291 | """ | ||
292 | Check for ".debug" files or directories outside of the dbg package | ||
293 | """ | ||
294 | |||
295 | if not "-dbg" in name and not "-ptest" in name: | ||
296 | if '.debug' in path.split(os.path.sep): | ||
297 | messages.append("non debug package contains .debug directory: %s path %s" % \ | ||
298 | (name, package_qa_clean_path(path,d))) | ||
299 | |||
300 | QAPATHTEST[perms] = "package_qa_check_perm" | ||
301 | def package_qa_check_perm(path,name,d, elf, messages): | ||
302 | """ | ||
303 | Check the permission of files | ||
304 | """ | ||
305 | return | ||
306 | |||
307 | QAPATHTEST[unsafe-references-in-binaries] = "package_qa_check_unsafe_references_in_binaries" | ||
308 | def package_qa_check_unsafe_references_in_binaries(path, name, d, elf, messages): | ||
309 | """ | ||
310 | Ensure binaries in base_[bindir|sbindir|libdir] do not link to files under exec_prefix | ||
311 | """ | ||
312 | if unsafe_references_skippable(path, name, d): | ||
313 | return | ||
314 | |||
315 | if elf: | ||
316 | import subprocess as sub | ||
317 | pn = d.getVar('PN', True) | ||
318 | |||
319 | exec_prefix = d.getVar('exec_prefix', True) | ||
320 | sysroot_path = d.getVar('STAGING_DIR_TARGET', True) | ||
321 | sysroot_path_usr = sysroot_path + exec_prefix | ||
322 | |||
323 | try: | ||
324 | ldd_output = bb.process.Popen(["prelink-rtld", "--root", sysroot_path, path], stdout=sub.PIPE).stdout.read() | ||
325 | except bb.process.CmdError: | ||
326 | error_msg = pn + ": prelink-rtld aborted when processing %s" % path | ||
327 | package_qa_handle_error("unsafe-references-in-binaries", error_msg, d) | ||
328 | return False | ||
329 | |||
330 | if sysroot_path_usr in ldd_output: | ||
331 | ldd_output = ldd_output.replace(sysroot_path, "") | ||
332 | |||
333 | pkgdest = d.getVar('PKGDEST', True) | ||
334 | packages = d.getVar('PACKAGES', True) | ||
335 | |||
336 | for package in packages.split(): | ||
337 | short_path = path.replace('%s/%s' % (pkgdest, package), "", 1) | ||
338 | if (short_path != path): | ||
339 | break | ||
340 | |||
341 | base_err = pn + ": %s, installed in the base_prefix, requires a shared library under exec_prefix (%s)" % (short_path, exec_prefix) | ||
342 | for line in ldd_output.split('\n'): | ||
343 | if exec_prefix in line: | ||
344 | error_msg = "%s: %s" % (base_err, line.strip()) | ||
345 | package_qa_handle_error("unsafe-references-in-binaries", error_msg, d) | ||
346 | |||
347 | return False | ||
348 | |||
349 | QAPATHTEST[unsafe-references-in-scripts] = "package_qa_check_unsafe_references_in_scripts" | ||
350 | def package_qa_check_unsafe_references_in_scripts(path, name, d, elf, messages): | ||
351 | """ | ||
352 | Warn if scripts in base_[bindir|sbindir|libdir] reference files under exec_prefix | ||
353 | """ | ||
354 | if unsafe_references_skippable(path, name, d): | ||
355 | return | ||
356 | |||
357 | if not elf: | ||
358 | import stat | ||
359 | import subprocess | ||
360 | pn = d.getVar('PN', True) | ||
361 | |||
362 | # Ensure we're checking an executable script | ||
363 | statinfo = os.stat(path) | ||
364 | if bool(statinfo.st_mode & stat.S_IXUSR): | ||
365 | # grep shell scripts for possible references to /exec_prefix/ | ||
366 | exec_prefix = d.getVar('exec_prefix', True) | ||
367 | statement = "grep -e '%s/' %s > /dev/null" % (exec_prefix, path) | ||
368 | if subprocess.call(statement, shell=True) == 0: | ||
369 | error_msg = pn + ": Found a reference to %s/ in %s" % (exec_prefix, path) | ||
370 | package_qa_handle_error("unsafe-references-in-scripts", error_msg, d) | ||
371 | error_msg = "Shell scripts in base_bindir and base_sbindir should not reference anything in exec_prefix" | ||
372 | package_qa_handle_error("unsafe-references-in-scripts", error_msg, d) | ||
373 | |||
374 | def unsafe_references_skippable(path, name, d): | ||
375 | if bb.data.inherits_class('native', d) or bb.data.inherits_class('nativesdk', d): | ||
376 | return True | ||
377 | |||
378 | if "-dbg" in name or "-dev" in name: | ||
379 | return True | ||
380 | |||
381 | # Other package names to skip: | ||
382 | if name.startswith("kernel-module-"): | ||
383 | return True | ||
384 | |||
385 | # Skip symlinks | ||
386 | if os.path.islink(path): | ||
387 | return True | ||
388 | |||
389 | # Skip unusual rootfs layouts which make these tests irrelevant | ||
390 | exec_prefix = d.getVar('exec_prefix', True) | ||
391 | if exec_prefix == "": | ||
392 | return True | ||
393 | |||
394 | pkgdest = d.getVar('PKGDEST', True) | ||
395 | pkgdest = pkgdest + "/" + name | ||
396 | pkgdest = os.path.abspath(pkgdest) | ||
397 | base_bindir = pkgdest + d.getVar('base_bindir', True) | ||
398 | base_sbindir = pkgdest + d.getVar('base_sbindir', True) | ||
399 | base_libdir = pkgdest + d.getVar('base_libdir', True) | ||
400 | bindir = pkgdest + d.getVar('bindir', True) | ||
401 | sbindir = pkgdest + d.getVar('sbindir', True) | ||
402 | libdir = pkgdest + d.getVar('libdir', True) | ||
403 | |||
404 | if base_bindir == bindir and base_sbindir == sbindir and base_libdir == libdir: | ||
405 | return True | ||
406 | |||
407 | # Skip files not in base_[bindir|sbindir|libdir] | ||
408 | path = os.path.abspath(path) | ||
409 | if not (base_bindir in path or base_sbindir in path or base_libdir in path): | ||
410 | return True | ||
411 | |||
412 | return False | ||
413 | |||
414 | QAPATHTEST[arch] = "package_qa_check_arch" | ||
415 | def package_qa_check_arch(path,name,d, elf, messages): | ||
416 | """ | ||
417 | Check if archs are compatible | ||
418 | """ | ||
419 | if not elf: | ||
420 | return | ||
421 | |||
422 | target_os = d.getVar('TARGET_OS', True) | ||
423 | target_arch = d.getVar('TARGET_ARCH', True) | ||
424 | provides = d.getVar('PROVIDES', True) | ||
425 | bpn = d.getVar('BPN', True) | ||
426 | |||
427 | # FIXME: Cross package confuse this check, so just skip them | ||
428 | for s in ['cross', 'nativesdk', 'cross-canadian']: | ||
429 | if bb.data.inherits_class(s, d): | ||
430 | return | ||
431 | |||
432 | # avoid following links to /usr/bin (e.g. on udev builds) | ||
433 | # we will check the files pointed to anyway... | ||
434 | if os.path.islink(path): | ||
435 | return | ||
436 | |||
437 | #if this will throw an exception, then fix the dict above | ||
438 | (machine, osabi, abiversion, littleendian, bits) \ | ||
439 | = package_qa_get_machine_dict()[target_os][target_arch] | ||
440 | |||
441 | # Check the architecture and endiannes of the binary | ||
442 | if not ((machine == elf.machine()) or \ | ||
443 | ("virtual/kernel" in provides) and (target_os == "linux-gnux32")): | ||
444 | messages.append("Architecture did not match (%d to %d) on %s" % \ | ||
445 | (machine, elf.machine(), package_qa_clean_path(path,d))) | ||
446 | elif not ((bits == elf.abiSize()) or \ | ||
447 | ("virtual/kernel" in provides) and (target_os == "linux-gnux32")): | ||
448 | messages.append("Bit size did not match (%d to %d) %s on %s" % \ | ||
449 | (bits, elf.abiSize(), bpn, package_qa_clean_path(path,d))) | ||
450 | elif not littleendian == elf.isLittleEndian(): | ||
451 | messages.append("Endiannes did not match (%d to %d) on %s" % \ | ||
452 | (littleendian, elf.isLittleEndian(), package_qa_clean_path(path,d))) | ||
453 | |||
454 | QAPATHTEST[desktop] = "package_qa_check_desktop" | ||
455 | def package_qa_check_desktop(path, name, d, elf, messages): | ||
456 | """ | ||
457 | Run all desktop files through desktop-file-validate. | ||
458 | """ | ||
459 | if path.endswith(".desktop"): | ||
460 | desktop_file_validate = os.path.join(d.getVar('STAGING_BINDIR_NATIVE',True),'desktop-file-validate') | ||
461 | output = os.popen("%s %s" % (desktop_file_validate, path)) | ||
462 | # This only produces output on errors | ||
463 | for l in output: | ||
464 | messages.append("Desktop file issue: " + l.strip()) | ||
465 | |||
466 | QAPATHTEST[textrel] = "package_qa_textrel" | ||
467 | def package_qa_textrel(path, name, d, elf, messages): | ||
468 | """ | ||
469 | Check if the binary contains relocations in .text | ||
470 | """ | ||
471 | |||
472 | if not elf: | ||
473 | return | ||
474 | |||
475 | if os.path.islink(path): | ||
476 | return | ||
477 | |||
478 | phdrs = elf.run_objdump("-p", d) | ||
479 | sane = True | ||
480 | |||
481 | import re | ||
482 | textrel_re = re.compile("\s+TEXTREL\s+") | ||
483 | for line in phdrs.split("\n"): | ||
484 | if textrel_re.match(line): | ||
485 | sane = False | ||
486 | |||
487 | if not sane: | ||
488 | messages.append("ELF binary '%s' has relocations in .text" % path) | ||
489 | |||
490 | QAPATHTEST[ldflags] = "package_qa_hash_style" | ||
491 | def package_qa_hash_style(path, name, d, elf, messages): | ||
492 | """ | ||
493 | Check if the binary has the right hash style... | ||
494 | """ | ||
495 | |||
496 | if not elf: | ||
497 | return | ||
498 | |||
499 | if os.path.islink(path): | ||
500 | return | ||
501 | |||
502 | gnu_hash = "--hash-style=gnu" in d.getVar('LDFLAGS', True) | ||
503 | if not gnu_hash: | ||
504 | gnu_hash = "--hash-style=both" in d.getVar('LDFLAGS', True) | ||
505 | if not gnu_hash: | ||
506 | return | ||
507 | |||
508 | sane = False | ||
509 | has_syms = False | ||
510 | |||
511 | phdrs = elf.run_objdump("-p", d) | ||
512 | |||
513 | # If this binary has symbols, we expect it to have GNU_HASH too. | ||
514 | for line in phdrs.split("\n"): | ||
515 | if "SYMTAB" in line: | ||
516 | has_syms = True | ||
517 | if "GNU_HASH" in line: | ||
518 | sane = True | ||
519 | if "[mips32]" in line or "[mips64]" in line: | ||
520 | sane = True | ||
521 | |||
522 | if has_syms and not sane: | ||
523 | messages.append("No GNU_HASH in the elf binary: '%s'" % path) | ||
524 | |||
525 | |||
526 | QAPATHTEST[buildpaths] = "package_qa_check_buildpaths" | ||
527 | def package_qa_check_buildpaths(path, name, d, elf, messages): | ||
528 | """ | ||
529 | Check for build paths inside target files and error if not found in the whitelist | ||
530 | """ | ||
531 | # Ignore .debug files, not interesting | ||
532 | if path.find(".debug") != -1: | ||
533 | return | ||
534 | |||
535 | # Ignore symlinks | ||
536 | if os.path.islink(path): | ||
537 | return | ||
538 | |||
539 | tmpdir = d.getVar('TMPDIR', True) | ||
540 | with open(path) as f: | ||
541 | file_content = f.read() | ||
542 | if tmpdir in file_content: | ||
543 | messages.append("File %s in package contained reference to tmpdir" % package_qa_clean_path(path,d)) | ||
544 | |||
545 | |||
546 | QAPATHTEST[xorg-driver-abi] = "package_qa_check_xorg_driver_abi" | ||
547 | def package_qa_check_xorg_driver_abi(path, name, d, elf, messages): | ||
548 | """ | ||
549 | Check that all packages containing Xorg drivers have ABI dependencies | ||
550 | """ | ||
551 | |||
552 | # Skip dev, dbg or nativesdk packages | ||
553 | if name.endswith("-dev") or name.endswith("-dbg") or name.startswith("nativesdk-"): | ||
554 | return | ||
555 | |||
556 | driverdir = d.expand("${libdir}/xorg/modules/drivers/") | ||
557 | if driverdir in path and path.endswith(".so"): | ||
558 | for rdep in bb.utils.explode_deps(d.getVar('RDEPENDS_' + name, True) or ""): | ||
559 | if rdep.startswith("xorg-abi-"): | ||
560 | return | ||
561 | messages.append("Package %s contains Xorg driver (%s) but no xorg-abi- dependencies" % (name, os.path.basename(path))) | ||
562 | |||
563 | QAPATHTEST[infodir] = "package_qa_check_infodir" | ||
564 | def package_qa_check_infodir(path, name, d, elf, messages): | ||
565 | """ | ||
566 | Check that /usr/share/info/dir isn't shipped in a particular package | ||
567 | """ | ||
568 | infodir = d.expand("${infodir}/dir") | ||
569 | |||
570 | if infodir in path: | ||
571 | messages.append("The /usr/share/info/dir file is not meant to be shipped in a particular package.") | ||
572 | |||
573 | def package_qa_check_license(workdir, d): | ||
574 | """ | ||
575 | Check for changes in the license files | ||
576 | """ | ||
577 | import tempfile | ||
578 | sane = True | ||
579 | |||
580 | lic_files = d.getVar('LIC_FILES_CHKSUM', True) | ||
581 | lic = d.getVar('LICENSE', True) | ||
582 | pn = d.getVar('PN', True) | ||
583 | |||
584 | if lic == "CLOSED": | ||
585 | return True | ||
586 | |||
587 | if not lic_files: | ||
588 | bb.error(pn + ": Recipe file does not have license file information (LIC_FILES_CHKSUM)") | ||
589 | return False | ||
590 | |||
591 | srcdir = d.getVar('S', True) | ||
592 | |||
593 | for url in lic_files.split(): | ||
594 | (type, host, path, user, pswd, parm) = bb.fetch.decodeurl(url) | ||
595 | srclicfile = os.path.join(srcdir, path) | ||
596 | if not os.path.isfile(srclicfile): | ||
597 | raise bb.build.FuncFailed( pn + ": LIC_FILES_CHKSUM points to an invalid file: " + srclicfile) | ||
598 | |||
599 | if 'md5' not in parm: | ||
600 | bb.error(pn + ": md5 checksum is not specified for ", url) | ||
601 | return False | ||
602 | beginline, endline = 0, 0 | ||
603 | if 'beginline' in parm: | ||
604 | beginline = int(parm['beginline']) | ||
605 | if 'endline' in parm: | ||
606 | endline = int(parm['endline']) | ||
607 | |||
608 | if (not beginline) and (not endline): | ||
609 | md5chksum = bb.utils.md5_file(srclicfile) | ||
610 | else: | ||
611 | fi = open(srclicfile, 'rb') | ||
612 | fo = tempfile.NamedTemporaryFile(mode='wb', prefix='poky.', suffix='.tmp', delete=False) | ||
613 | tmplicfile = fo.name; | ||
614 | lineno = 0 | ||
615 | linesout = 0 | ||
616 | for line in fi: | ||
617 | lineno += 1 | ||
618 | if (lineno >= beginline): | ||
619 | if ((lineno <= endline) or not endline): | ||
620 | fo.write(line) | ||
621 | linesout += 1 | ||
622 | else: | ||
623 | break | ||
624 | fo.flush() | ||
625 | fo.close() | ||
626 | fi.close() | ||
627 | md5chksum = bb.utils.md5_file(tmplicfile) | ||
628 | os.unlink(tmplicfile) | ||
629 | |||
630 | if parm['md5'] == md5chksum: | ||
631 | bb.note (pn + ": md5 checksum matched for ", url) | ||
632 | else: | ||
633 | bb.error (pn + ": md5 data is not matching for ", url) | ||
634 | bb.error (pn + ": The new md5 checksum is ", md5chksum) | ||
635 | bb.error (pn + ": Check if the license information has changed in") | ||
636 | sane = False | ||
637 | |||
638 | return sane | ||
639 | |||
640 | def package_qa_check_staged(path,d): | ||
641 | """ | ||
642 | Check staged la and pc files for sanity | ||
643 | -e.g. installed being false | ||
644 | |||
645 | As this is run after every stage we should be able | ||
646 | to find the one responsible for the errors easily even | ||
647 | if we look at every .pc and .la file | ||
648 | """ | ||
649 | |||
650 | sane = True | ||
651 | tmpdir = d.getVar('TMPDIR', True) | ||
652 | workdir = os.path.join(tmpdir, "work") | ||
653 | |||
654 | installed = "installed=yes" | ||
655 | if bb.data.inherits_class("native", d) or bb.data.inherits_class("cross", d): | ||
656 | pkgconfigcheck = workdir | ||
657 | else: | ||
658 | pkgconfigcheck = tmpdir | ||
659 | |||
660 | # find all .la and .pc files | ||
661 | # read the content | ||
662 | # and check for stuff that looks wrong | ||
663 | for root, dirs, files in os.walk(path): | ||
664 | for file in files: | ||
665 | path = os.path.join(root,file) | ||
666 | if file.endswith(".la"): | ||
667 | with open(path) as f: | ||
668 | file_content = f.read() | ||
669 | if workdir in file_content: | ||
670 | error_msg = "%s failed sanity test (workdir) in path %s" % (file,root) | ||
671 | sane = package_qa_handle_error("la", error_msg, d) | ||
672 | elif file.endswith(".pc"): | ||
673 | with open(path) as f: | ||
674 | file_content = f.read() | ||
675 | if pkgconfigcheck in file_content: | ||
676 | error_msg = "%s failed sanity test (tmpdir) in path %s" % (file,root) | ||
677 | sane = package_qa_handle_error("pkgconfig", error_msg, d) | ||
678 | |||
679 | return sane | ||
680 | |||
681 | # Walk over all files in a directory and call func | ||
682 | def package_qa_walk(path, warnfuncs, errorfuncs, skip, package, d): | ||
683 | import oe.qa | ||
684 | |||
685 | #if this will throw an exception, then fix the dict above | ||
686 | target_os = d.getVar('TARGET_OS', True) | ||
687 | target_arch = d.getVar('TARGET_ARCH', True) | ||
688 | |||
689 | warnings = [] | ||
690 | errors = [] | ||
691 | for path in pkgfiles[package]: | ||
692 | elf = oe.qa.ELFFile(path) | ||
693 | try: | ||
694 | elf.open() | ||
695 | except: | ||
696 | elf = None | ||
697 | for func in warnfuncs: | ||
698 | func(path, package, d, elf, warnings) | ||
699 | for func in errorfuncs: | ||
700 | func(path, package, d, elf, errors) | ||
701 | |||
702 | for w in warnings: | ||
703 | bb.warn("QA Issue: %s" % w) | ||
704 | package_qa_write_error(w, d) | ||
705 | for e in errors: | ||
706 | bb.error("QA Issue: %s" % e) | ||
707 | package_qa_write_error(e, d) | ||
708 | |||
709 | return len(errors) == 0 | ||
710 | |||
711 | def package_qa_check_rdepends(pkg, pkgdest, skip, d): | ||
712 | # Don't do this check for kernel/module recipes, there aren't too many debug/development | ||
713 | # packages and you can get false positives e.g. on kernel-module-lirc-dev | ||
714 | if bb.data.inherits_class("kernel", d) or bb.data.inherits_class("module-base", d): | ||
715 | return True | ||
716 | |||
717 | sane = True | ||
718 | if not "-dbg" in pkg and not "packagegroup-" in pkg and not "-image" in pkg: | ||
719 | localdata = bb.data.createCopy(d) | ||
720 | localdata.setVar('OVERRIDES', pkg) | ||
721 | bb.data.update_data(localdata) | ||
722 | |||
723 | # Now check the RDEPENDS | ||
724 | rdepends = bb.utils.explode_deps(localdata.getVar('RDEPENDS', True) or "") | ||
725 | |||
726 | # Now do the sanity check!!! | ||
727 | for rdepend in rdepends: | ||
728 | if "-dbg" in rdepend and "debug-deps" not in skip: | ||
729 | error_msg = "%s rdepends on %s" % (pkg,rdepend) | ||
730 | sane = package_qa_handle_error("debug-deps", error_msg, d) | ||
731 | if (not "-dev" in pkg and not "-staticdev" in pkg) and rdepend.endswith("-dev") and "dev-deps" not in skip: | ||
732 | error_msg = "%s rdepends on %s" % (pkg, rdepend) | ||
733 | sane = package_qa_handle_error("dev-deps", error_msg, d) | ||
734 | |||
735 | return sane | ||
736 | |||
737 | def package_qa_check_deps(pkg, pkgdest, skip, d): | ||
738 | sane = True | ||
739 | |||
740 | localdata = bb.data.createCopy(d) | ||
741 | localdata.setVar('OVERRIDES', pkg) | ||
742 | bb.data.update_data(localdata) | ||
743 | |||
744 | def check_valid_deps(var): | ||
745 | sane = True | ||
746 | try: | ||
747 | rvar = bb.utils.explode_dep_versions2(localdata.getVar(var, True) or "") | ||
748 | except ValueError as e: | ||
749 | bb.fatal("%s_%s: %s" % (var, pkg, e)) | ||
750 | for dep in rvar: | ||
751 | for v in rvar[dep]: | ||
752 | if v and not v.startswith(('< ', '= ', '> ', '<= ', '>=')): | ||
753 | error_msg = "%s_%s is invalid: %s (%s) only comparisons <, =, >, <=, and >= are allowed" % (var, pkg, dep, v) | ||
754 | sane = package_qa_handle_error("dep-cmp", error_msg, d) | ||
755 | return sane | ||
756 | |||
757 | sane = True | ||
758 | if not check_valid_deps('RDEPENDS'): | ||
759 | sane = False | ||
760 | if not check_valid_deps('RRECOMMENDS'): | ||
761 | sane = False | ||
762 | if not check_valid_deps('RSUGGESTS'): | ||
763 | sane = False | ||
764 | if not check_valid_deps('RPROVIDES'): | ||
765 | sane = False | ||
766 | if not check_valid_deps('RREPLACES'): | ||
767 | sane = False | ||
768 | if not check_valid_deps('RCONFLICTS'): | ||
769 | sane = False | ||
770 | |||
771 | return sane | ||
772 | |||
773 | # The PACKAGE FUNC to scan each package | ||
774 | python do_package_qa () { | ||
775 | import subprocess | ||
776 | |||
777 | bb.note("DO PACKAGE QA") | ||
778 | |||
779 | logdir = d.getVar('T', True) | ||
780 | pkg = d.getVar('PN', True) | ||
781 | |||
782 | # Check the compile log for host contamination | ||
783 | compilelog = os.path.join(logdir,"log.do_compile") | ||
784 | |||
785 | if os.path.exists(compilelog): | ||
786 | statement = "grep -e 'CROSS COMPILE Badness:' -e 'is unsafe for cross-compilation' %s > /dev/null" % compilelog | ||
787 | if subprocess.call(statement, shell=True) == 0: | ||
788 | msg = "%s: The compile log indicates that host include and/or library paths were used.\n \ | ||
789 | Please check the log '%s' for more information." % (pkg, compilelog) | ||
790 | package_qa_handle_error("compile-host-path", msg, d) | ||
791 | |||
792 | # Check the install log for host contamination | ||
793 | installlog = os.path.join(logdir,"log.do_install") | ||
794 | |||
795 | if os.path.exists(installlog): | ||
796 | statement = "grep -e 'CROSS COMPILE Badness:' -e 'is unsafe for cross-compilation' %s > /dev/null" % installlog | ||
797 | if subprocess.call(statement, shell=True) == 0: | ||
798 | msg = "%s: The install log indicates that host include and/or library paths were used.\n \ | ||
799 | Please check the log '%s' for more information." % (pkg, installlog) | ||
800 | package_qa_handle_error("install-host-path", msg, d) | ||
801 | |||
802 | # Scan the packages... | ||
803 | pkgdest = d.getVar('PKGDEST', True) | ||
804 | packages = d.getVar('PACKAGES', True) | ||
805 | |||
806 | # no packages should be scanned | ||
807 | if not packages: | ||
808 | return | ||
809 | |||
810 | testmatrix = d.getVarFlags("QAPATHTEST") | ||
811 | import re | ||
812 | # The package name matches the [a-z0-9.+-]+ regular expression | ||
813 | pkgname_pattern = re.compile("^[a-z0-9.+-]+$") | ||
814 | |||
815 | g = globals() | ||
816 | walk_sane = True | ||
817 | rdepends_sane = True | ||
818 | deps_sane = True | ||
819 | for package in packages.split(): | ||
820 | skip = (d.getVar('INSANE_SKIP_' + package, True) or "").split() | ||
821 | if skip: | ||
822 | bb.note("Package %s skipping QA tests: %s" % (package, str(skip))) | ||
823 | warnchecks = [] | ||
824 | for w in (d.getVar("WARN_QA", True) or "").split(): | ||
825 | if w in skip: | ||
826 | continue | ||
827 | if w in testmatrix and testmatrix[w] in g: | ||
828 | warnchecks.append(g[testmatrix[w]]) | ||
829 | errorchecks = [] | ||
830 | for e in (d.getVar("ERROR_QA", True) or "").split(): | ||
831 | if e in skip: | ||
832 | continue | ||
833 | if e in testmatrix and testmatrix[e] in g: | ||
834 | errorchecks.append(g[testmatrix[e]]) | ||
835 | |||
836 | bb.note("Checking Package: %s" % package) | ||
837 | # Check package name | ||
838 | if not pkgname_pattern.match(package): | ||
839 | package_qa_handle_error("pkgname", | ||
840 | "%s doesn't match the [a-z0-9.+-]+ regex\n" % package, d) | ||
841 | |||
842 | path = "%s/%s" % (pkgdest, package) | ||
843 | if not package_qa_walk(path, warnchecks, errorchecks, skip, package, d): | ||
844 | walk_sane = False | ||
845 | if not package_qa_check_rdepends(package, pkgdest, skip, d): | ||
846 | rdepends_sane = False | ||
847 | if not package_qa_check_deps(package, pkgdest, skip, d): | ||
848 | deps_sane = False | ||
849 | |||
850 | |||
851 | if 'libdir' in d.getVar("ALL_QA", True).split(): | ||
852 | package_qa_check_libdir(d) | ||
853 | |||
854 | qa_sane = d.getVar("QA_SANE", True) | ||
855 | if not walk_sane or not rdepends_sane or not deps_sane or not qa_sane: | ||
856 | bb.fatal("QA run found fatal errors. Please consider fixing them.") | ||
857 | bb.note("DONE with PACKAGE QA") | ||
858 | } | ||
859 | |||
860 | |||
861 | python do_qa_staging() { | ||
862 | bb.note("QA checking staging") | ||
863 | |||
864 | if not package_qa_check_staged(d.expand('${SYSROOT_DESTDIR}/${STAGING_LIBDIR}'), d): | ||
865 | bb.fatal("QA staging was broken by the package built above") | ||
866 | } | ||
867 | |||
868 | python do_qa_configure() { | ||
869 | import subprocess | ||
870 | |||
871 | ########################################################################### | ||
872 | # Check config.log for cross compile issues | ||
873 | ########################################################################### | ||
874 | |||
875 | configs = [] | ||
876 | workdir = d.getVar('WORKDIR', True) | ||
877 | bb.note("Checking autotools environment for common misconfiguration") | ||
878 | for root, dirs, files in os.walk(workdir): | ||
879 | statement = "grep -e 'CROSS COMPILE Badness:' -e 'is unsafe for cross-compilation' %s > /dev/null" % \ | ||
880 | os.path.join(root,"config.log") | ||
881 | if "config.log" in files: | ||
882 | if subprocess.call(statement, shell=True) == 0: | ||
883 | bb.fatal("""This autoconf log indicates errors, it looked at host include and/or library paths while determining system capabilities. | ||
884 | Rerun configure task after fixing this. The path was '%s'""" % root) | ||
885 | |||
886 | if "configure.ac" in files: | ||
887 | configs.append(os.path.join(root,"configure.ac")) | ||
888 | if "configure.in" in files: | ||
889 | configs.append(os.path.join(root, "configure.in")) | ||
890 | |||
891 | ########################################################################### | ||
892 | # Check gettext configuration and dependencies are correct | ||
893 | ########################################################################### | ||
894 | |||
895 | cnf = d.getVar('EXTRA_OECONF', True) or "" | ||
896 | if "gettext" not in d.getVar('P', True) and "gcc-runtime" not in d.getVar('P', True) and "--disable-nls" not in cnf: | ||
897 | ml = d.getVar("MLPREFIX", True) or "" | ||
898 | if bb.data.inherits_class('native', d) or bb.data.inherits_class('cross', d) or bb.data.inherits_class('crosssdk', d) or bb.data.inherits_class('nativesdk', d): | ||
899 | gt = "gettext-native" | ||
900 | elif bb.data.inherits_class('cross-canadian', d): | ||
901 | gt = "nativesdk-gettext" | ||
902 | else: | ||
903 | gt = "virtual/" + ml + "gettext" | ||
904 | deps = bb.utils.explode_deps(d.getVar('DEPENDS', True) or "") | ||
905 | if gt not in deps: | ||
906 | for config in configs: | ||
907 | gnu = "grep \"^[[:space:]]*AM_GNU_GETTEXT\" %s >/dev/null" % config | ||
908 | if subprocess.call(gnu, shell=True) == 0: | ||
909 | bb.fatal("""%s required but not in DEPENDS for file %s. | ||
910 | Missing inherit gettext?""" % (gt, config)) | ||
911 | |||
912 | ########################################################################### | ||
913 | # Check license variables | ||
914 | ########################################################################### | ||
915 | |||
916 | if not package_qa_check_license(workdir, d): | ||
917 | bb.fatal("Licensing Error: LIC_FILES_CHKSUM does not match, please fix") | ||
918 | |||
919 | } | ||
920 | # The Staging Func, to check all staging | ||
921 | #addtask qa_staging after do_populate_sysroot before do_build | ||
922 | do_populate_sysroot[postfuncs] += "do_qa_staging " | ||
923 | |||
924 | # Check broken config.log files, for packages requiring Gettext which don't | ||
925 | # have it in DEPENDS and for correct LIC_FILES_CHKSUM | ||
926 | #addtask qa_configure after do_configure before do_compile | ||
927 | do_configure[postfuncs] += "do_qa_configure " | ||
928 | |||
929 | python () { | ||
930 | tests = d.getVar('ALL_QA', True).split() | ||
931 | if "desktop" in tests: | ||
932 | d.appendVar("PACKAGE_DEPENDS", "desktop-file-utils-native") | ||
933 | |||
934 | ########################################################################### | ||
935 | # Check various variables | ||
936 | ########################################################################### | ||
937 | |||
938 | if d.getVar('do_stage', True) is not None: | ||
939 | bb.fatal("Legacy staging found for %s as it has a do_stage function. This will need conversion to a do_install or often simply removal to work with OE-core" % d.getVar("FILE", True)) | ||
940 | |||
941 | overrides = d.getVar('OVERRIDES', True).split(':') | ||
942 | pn = d.getVar('PN', True) | ||
943 | if pn in overrides: | ||
944 | msg = 'Recipe %s has PN of "%s" which is in OVERRIDES, this can result in unexpected behaviour.' % (d.getVar("FILE", True), pn) | ||
945 | package_qa_handle_error("pn-overrides", msg, d) | ||
946 | |||
947 | issues = [] | ||
948 | if (d.getVar('PACKAGES', True) or "").split(): | ||
949 | for var in 'RDEPENDS', 'RRECOMMENDS', 'RSUGGESTS', 'RCONFLICTS', 'RPROVIDES', 'RREPLACES', 'FILES', 'pkg_preinst', 'pkg_postinst', 'pkg_prerm', 'pkg_postrm', 'ALLOW_EMPTY': | ||
950 | if d.getVar(var): | ||
951 | issues.append(var) | ||
952 | for i in issues: | ||
953 | package_qa_handle_error("pkgvarcheck", "%s: Variable %s is set as not being package specific, please fix this." % (d.getVar("FILE", True), i), d) | ||
954 | } | ||