1 # BB Class inspired by ebuild.sh
3 # This class will test files after installation for certain
4 # security issues and other kind of issues.
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
17 # We need to have the scanelf utility as soon as
18 # possible and this is contained within the pax-utils-native.
19 # The package.bbclass can help us here.
22 PACKAGE_DEPENDS += "pax-utils-native desktop-file-utils-native"
23 PACKAGEFUNCS += " do_package_qa "
27 # dictionary for elf headers
29 # feel free to add and correct.
31 # TARGET_OS TARGET_ARCH MACHINE, OSABI, ABIVERSION, Little Endian, 32bit?
32 def package_qa_get_machine_dict():
35 "arm" : (40, 97, 0, True, True),
36 "armeb": (40, 97, 0, False, True),
37 "powerpc": (20, 0, 0, False, True),
38 "i386": ( 3, 0, 0, True, True),
39 "i486": ( 3, 0, 0, True, True),
40 "i586": ( 3, 0, 0, True, True),
41 "i686": ( 3, 0, 0, True, True),
42 "x86_64": (62, 0, 0, True, False),
43 "ia64": (50, 0, 0, True, False),
44 "alpha": (36902, 0, 0, True, False),
45 "hppa": (15, 3, 0, False, True),
46 "m68k": ( 4, 0, 0, False, True),
47 "mips": ( 8, 0, 0, False, True),
48 "mipsel": ( 8, 0, 0, True, True),
49 "s390": (22, 0, 0, False, True),
50 "sh4": (42, 0, 0, True, True),
51 "sparc": ( 2, 0, 0, False, True),
54 "arm" : ( 40, 97, 0, True, True),
55 "armeb": ( 40, 97, 0, False, True),
56 "powerpc": ( 20, 0, 0, False, True),
57 "i386": ( 3, 0, 0, True, True),
58 "i486": ( 3, 0, 0, True, True),
59 "i586": ( 3, 0, 0, True, True),
60 "i686": ( 3, 0, 0, True, True),
61 "mipsel": ( 8, 0, 0, True, True),
62 "avr32": (6317, 0, 0, False, True),
65 "bfin": ( 106, 0, 0, True, True),
68 "arm" : (40, 0, 0, True, True),
69 "armeb" : (40, 0, 0, False, True),
71 "linux-uclibcgnueabi" : {
72 "arm" : (40, 0, 0, True, True),
73 "armeb" : (40, 0, 0, False, True),
78 # factory for a class, embedded in a method
79 def package_qa_get_elf(path, bits32):
89 # possible values for EI_CLASS
94 # possible value for EI_VERSION
97 # possible values for EI_DATA
102 def my_assert(expectation, result):
103 if not expectation == result:
104 #print "'%x','%x'" % (ord(expectation), ord(result))
105 raise Exception("This does not work as expected")
106 my_assert = staticmethod(my_assert)
108 def __init__(self, name):
112 self.file = file(self.name, "r")
113 self.data = self.file.read(ELFFile.EI_NIDENT+4)
115 ELFFile.my_assert(len(self.data), ELFFile.EI_NIDENT+4)
116 ELFFile.my_assert(self.data[0], chr(0x7f) )
117 ELFFile.my_assert(self.data[1], 'E')
118 ELFFile.my_assert(self.data[2], 'L')
119 ELFFile.my_assert(self.data[3], 'F')
121 ELFFile.my_assert(self.data[ELFFile.EI_CLASS], chr(ELFFile.ELFCLASS32))
123 ELFFile.my_assert(self.data[ELFFile.EI_CLASS], chr(ELFFile.ELFCLASS64))
124 ELFFile.my_assert(self.data[ELFFile.EI_VERSION], chr(ELFFile.EV_CURRENT) )
126 self.sex = self.data[ELFFile.EI_DATA]
127 if self.sex == chr(ELFFile.ELFDATANONE):
128 raise Exception("self.sex == ELFDATANONE")
129 elif self.sex == chr(ELFFile.ELFDATA2LSB):
131 elif self.sex == chr(ELFFile.ELFDATA2MSB):
134 raise Exception("Unknown self.sex")
137 return ord(self.data[ELFFile.EI_OSABI])
139 def abiVersion(self):
140 return ord(self.data[ELFFile.EI_ABIVERSION])
142 def isLittleEndian(self):
143 return self.sex == "<"
145 def isBigEngian(self):
146 return self.sex == ">"
150 We know the sex stored in self.sex and we
154 (a,) = struct.unpack(self.sex+"H", self.data[18:20])
160 # Known Error classes
161 # 0 - non dev contains .so
162 # 1 - package contains a dangerous RPATH
163 # 2 - package depends on debug package
164 # 3 - non dbg contains .so
165 # 4 - wrong architecture
166 # 5 - .la contains installed=yes or reference to the workdir
167 # 6 - .pc contains reference to /usr/include or workdir
168 # 7 - the desktop file is not valid
170 def package_qa_clean_path(path,d):
171 """ Remove the common prefix from the path. In this case it is the TMPDIR"""
173 return path.replace(bb.data.getVar('TMPDIR',d,True),"")
175 def package_qa_make_fatal_error(error_class, name, path,d):
177 decide if an error is fatal
179 TODO: Load a whitelist of known errors
181 if error_class == 0 or error_class == 5:
186 def package_qa_write_error(error_class, name, path, d):
188 if not bb.data.getVar('QA_LOG', d):
192 "non dev contains .so",
193 "package contains RPATH",
194 "package depends on debug package",
195 "non dbg contains .debug",
196 "wrong architecture",
197 "evil hides inside the .la",
198 "evil hides inside the .pc",
202 log_path = os.path.join( bb.data.getVar('T', d, True), "log.qa_package" )
203 f = file( log_path, "a+")
204 print >> f, "%s, %s, %s" % \
205 (ERROR_NAMES[error_class], name, package_qa_clean_path(path,d))
209 def package_qa_check_rpath(file,name,d):
211 Check for dangerous RPATHs
214 scanelf = os.path.join(bb.data.getVar('STAGING_BINDIR_NATIVE',d,True),'scanelf')
215 bad_dir = bb.data.getVar('TMPDIR', d, True) + "/work"
216 bad_dir_test = bb.data.getVar('TMPDIR', d, True)
217 if not os.path.exists(scanelf):
218 bb.fatal("Can not check RPATH, scanelf (part of pax-utils-native) not found")
220 if not bad_dir in bb.data.getVar('WORKDIR', d, True):
221 bb.fatal("This class assumed that WORKDIR is ${TMPDIR}/work... Not doing any check")
223 output = os.popen("%s -B -F%%r#F '%s'" % (scanelf,file))
224 txt = output.readline().split()
227 package_qa_write_error( 1, name, file, d)
228 bb.error("QA Issue package %s contains bad RPATH %s in file %s" % \
232 def package_qa_check_devdbg(path, name,d):
234 Check for debug remains inside the binary or
235 non dev packages containing
241 if not "-dev" in name:
242 if path[-3:] == ".so" and os.path.islink(path):
243 package_qa_write_error( 0, name, path, d )
244 bb.error("QA Issue: non -dev package contains symlink .so: %s path '%s'" % \
245 (name, package_qa_clean_path(path,d)))
246 if package_qa_make_fatal_error( 0, name, path, d ):
249 if not "-dbg" in name:
251 package_qa_write_error( 3, name, path, d )
252 bb.error("QA Issue: non debug package contains .debug directory: %s path %s" % \
253 (name, package_qa_clean_path(path,d)))
254 if package_qa_make_fatal_error( 3, name, path, d ):
259 def package_qa_check_perm(path,name,d):
261 Check the permission of files
266 def package_qa_check_arch(path,name,d):
268 Check if archs are compatible
271 target_os = bb.data.getVar('TARGET_OS', d, True)
272 target_arch = bb.data.getVar('TARGET_ARCH', d, True)
274 # FIXME: Cross package confuse this check, so just skip them
275 if bb.data.inherits_class('cross', d) or bb.data.inherits_class('sdk', d):
278 # avoid following links to /usr/bin (e.g. on udev builds)
279 # we will check the files pointed to anyway...
280 if os.path.islink(path):
283 #if this will throw an exception, then fix the dict above
284 (machine, osabi, abiversion, littleendian, bits32) \
285 = package_qa_get_machine_dict()[target_os][target_arch]
286 elf = package_qa_get_elf(path, bits32)
289 # Check the architecture and endiannes of the binary
290 if not machine == elf.machine():
291 bb.error("Architecture did not match (%d to %d) on %s" % \
292 (machine, elf.machine(), package_qa_clean_path(path,d)))
293 return not package_qa_make_fatal_error( 4, name, path, d )
294 elif not littleendian == elf.isLittleEndian():
295 bb.error("Endiannes did not match (%d to %d) on %s" % \
296 (littleendian, elf.isLittleEndian(), package_qa_clean_path(path,d)))
297 return not package_qa_make_fatal_error( 4, name, path, d )
301 def package_qa_check_desktop(path, name, d):
303 Run all desktop files through desktop-file-validate.
306 if path.endswith(".desktop"):
307 validate = os.path.join(bb.data.getVar('STAGING_BINDIR_NATIVE',d,True), \
308 'desktop-file-validate')
309 output = os.popen("%s %s" % (validate, path))
312 return not package_qa_make_fatal_error(7, name, path, d)
315 def package_qa_check_staged(path,d):
317 Check staged la and pc files for sanity
318 -e.g. installed being false
320 As this is run after every stage we should be able
321 to find the one responsible for the errors easily even
322 if we look at every .pc and .la file
327 workdir = os.path.join(bb.data.getVar('TMPDIR', d, True), "work")
329 if bb.data.inherits_class("native", d):
330 installed = "installed=no"
332 installed = "installed=yes"
334 # find all .la and .pc files
336 # and check for stuff that looks wrong
337 for root, dirs, files in os.walk(path):
339 path = os.path.join(root,file)
340 if file[-2:] == "la":
341 file_content = open(path).read()
342 if installed in file_content:
343 bb.error("QA issue: %s failed sanity test (installed) in path %s" % \
345 if package_qa_make_fatal_error( 5, "staging", path, d):
347 if workdir in file_content:
348 bb.error("QA issue: %s failed sanity test (workdir) in path %s" % \
350 if package_qa_make_fatal_error( 5, "staging", path, d):
352 elif file[-2:] == "pc":
353 file_content = open(path).read()
354 if workdir in file_content:
355 bb.error("QA issue: %s failed sanity test (workdir) in path %s" % \
357 if package_qa_make_fatal_error( 6, "staging", path, d):
362 # Walk over all files in a directory and call func
363 def package_qa_walk(path, funcs, package,d):
367 for root, dirs, files in os.walk(path):
369 path = os.path.join(root,file)
371 if not func(path, package,d):
377 def package_qa_check_rdepends(pkg, workdir, d):
380 if not "-dbg" in pkg and not "task-" in pkg and not "-image" in pkg:
381 # Copied from package_ipk.bbclass
382 # boiler plate to update the data
383 localdata = bb.data.createCopy(d)
384 root = "%s/install/%s" % (workdir, pkg)
386 bb.data.setVar('ROOT', '', localdata)
387 bb.data.setVar('ROOT_%s' % pkg, root, localdata)
388 pkgname = bb.data.getVar('PKG_%s' % pkg, localdata, True)
391 bb.data.setVar('PKG', pkgname, localdata)
393 overrides = bb.data.getVar('OVERRIDES', localdata)
395 raise bb.build.FuncFailed('OVERRIDES not defined')
396 overrides = bb.data.expand(overrides, localdata)
397 bb.data.setVar('OVERRIDES', overrides + ':' + pkg, localdata)
399 bb.data.update_data(localdata)
401 # Now check the RDEPENDS
402 rdepends = explode_deps(bb.data.getVar('RDEPENDS', localdata, True) or "")
405 # Now do the sanity check!!!
406 for rdepend in rdepends:
407 if "-dbg" in rdepend:
408 package_qa_write_error( 2, pkgname, rdepend, d )
409 bb.error("QA issue: %s rdepends on %s" % (pkgname,rdepend))
410 if package_qa_make_fatal_error( 2, pkgname, rdepend, d ):
415 # The PACKAGE FUNC to scan each package
416 python do_package_qa () {
417 bb.note("DO PACKAGE QA")
418 workdir = bb.data.getVar('WORKDIR', d, True)
419 packages = bb.data.getVar('PACKAGES',d, True)
421 # no packages should be scanned
425 checks = [package_qa_check_rpath, package_qa_check_devdbg,
426 package_qa_check_perm, package_qa_check_arch,
427 package_qa_check_desktop]
430 for package in packages.split():
431 if bb.data.getVar('INSANE_SKIP_' + package, d, True):
432 bb.note("Package: %s (skipped)" % package)
435 bb.note("Checking Package: %s" % package)
436 path = "%s/install/%s" % (workdir, package)
437 if not package_qa_walk(path, checks, package, d):
439 if not package_qa_check_rdepends(package, workdir, d):
440 rdepends_sane = False
442 if not walk_sane or not rdepends_sane:
443 bb.fatal("QA run found fatal errors. Please consider fixing them.")
444 bb.note("DONE with PACKAGE QA")
448 # The Staging Func, to check all staging
449 addtask qa_staging after do_populate_staging before do_build
450 python do_qa_staging() {
451 bb.note("QA checking staging")
453 if not package_qa_check_staged(bb.data.getVar('STAGING_LIBDIR',d,True), d):
454 bb.fatal("QA staging was broken by the package built above")
457 # Check broken config.log files
458 addtask qa_configure after do_configure before do_compile
459 python do_qa_configure() {
460 bb.note("Checking sanity of the config.log file")
462 for root, dirs, files in os.walk(bb.data.getVar('WORKDIR', d, True)):
463 statement = "grep 'CROSS COMPILE Badness:' %s > /dev/null" % \
464 os.path.join(root,"config.log")
465 if "config.log" in files:
466 if os.system(statement) == 0:
467 bb.fatal("This autoconf log indicates errors, it looked at \
468 host includes. Rerun configure task after fixing this. \
469 Path was '%s'" % root)