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),
63 "sh4": (42, 0, 0, True, True),
67 "bfin": ( 106, 0, 0, True, True),
70 "arm" : (40, 0, 0, True, True),
71 "armeb" : (40, 0, 0, False, True),
73 "linux-uclibcgnueabi" : {
74 "arm" : (40, 0, 0, True, True),
75 "armeb" : (40, 0, 0, False, True),
78 "powerpc": (20, 0, 0, False, True),
83 # factory for a class, embedded in a method
84 def package_qa_get_elf(path, bits32):
94 # possible values for EI_CLASS
99 # possible value for EI_VERSION
102 # possible values for EI_DATA
107 def my_assert(self, expectation, result):
108 if not expectation == result:
109 #print "'%x','%x' %s" % (ord(expectation), ord(result), self.name)
110 raise Exception("This does not work as expected")
112 def __init__(self, name):
116 self.file = file(self.name, "r")
117 self.data = self.file.read(ELFFile.EI_NIDENT+4)
119 self.my_assert(len(self.data), ELFFile.EI_NIDENT+4)
120 self.my_assert(self.data[0], chr(0x7f) )
121 self.my_assert(self.data[1], 'E')
122 self.my_assert(self.data[2], 'L')
123 self.my_assert(self.data[3], 'F')
125 self.my_assert(self.data[ELFFile.EI_CLASS], chr(ELFFile.ELFCLASS32))
127 self.my_assert(self.data[ELFFile.EI_CLASS], chr(ELFFile.ELFCLASS64))
128 self.my_assert(self.data[ELFFile.EI_VERSION], chr(ELFFile.EV_CURRENT) )
130 self.sex = self.data[ELFFile.EI_DATA]
131 if self.sex == chr(ELFFile.ELFDATANONE):
132 raise Exception("self.sex == ELFDATANONE")
133 elif self.sex == chr(ELFFile.ELFDATA2LSB):
135 elif self.sex == chr(ELFFile.ELFDATA2MSB):
138 raise Exception("Unknown self.sex")
141 return ord(self.data[ELFFile.EI_OSABI])
143 def abiVersion(self):
144 return ord(self.data[ELFFile.EI_ABIVERSION])
146 def isLittleEndian(self):
147 return self.sex == "<"
149 def isBigEngian(self):
150 return self.sex == ">"
154 We know the sex stored in self.sex and we
158 (a,) = struct.unpack(self.sex+"H", self.data[18:20])
164 # Known Error classes
165 # 0 - non dev contains .so
166 # 1 - package contains a dangerous RPATH
167 # 2 - package depends on debug package
168 # 3 - non dbg contains .so
169 # 4 - wrong architecture
170 # 5 - .la contains installed=yes or reference to the workdir
171 # 6 - .pc contains reference to /usr/include or workdir
172 # 7 - the desktop file is not valid
173 # 8 - .la contains reference to the workdir
175 def package_qa_clean_path(path,d):
176 """ Remove the common prefix from the path. In this case it is the TMPDIR"""
178 return path.replace(bb.data.getVar('TMPDIR',d,True),"")
180 def package_qa_make_fatal_error(error_class, name, path,d):
182 decide if an error is fatal
184 TODO: Load a whitelist of known errors
186 return not error_class in [0, 5, 7]
188 def package_qa_write_error(error_class, name, path, d):
193 if not bb.data.getVar('QA_LOG', d):
194 bb.note("a QA error occured but will not be logged because QA_LOG is not set")
198 "non dev contains .so",
199 "package contains RPATH",
200 "package depends on debug package",
201 "non dbg contains .debug",
202 "wrong architecture",
203 "evil hides inside the .la",
204 "evil hides inside the .pc",
205 "the desktop file is not valid",
206 ".la contains reference to the workdir",
209 log_path = os.path.join( bb.data.getVar('T', d, True), "log.qa_package" )
210 f = file( log_path, "a+")
211 print >> f, "%s, %s, %s" % \
212 (ERROR_NAMES[error_class], name, package_qa_clean_path(path,d))
215 def package_qa_handle_error(error_class, error_msg, name, path, d):
217 bb.error("QA Issue: %s" % error_msg)
218 package_qa_write_error(error_class, name, path, d)
219 return not package_qa_make_fatal_error(error_class, name, path, d)
221 def package_qa_check_rpath(file,name,d):
223 Check for dangerous RPATHs
227 scanelf = os.path.join(bb.data.getVar('STAGING_BINDIR_NATIVE',d,True),'scanelf')
228 bad_dir = bb.data.getVar('TMPDIR', d, True) + "/work"
229 bad_dir_test = bb.data.getVar('TMPDIR', d, True)
230 if not os.path.exists(scanelf):
231 bb.fatal("Can not check RPATH, scanelf (part of pax-utils-native) not found")
233 if not bad_dir in bb.data.getVar('WORKDIR', d, True):
234 bb.fatal("This class assumed that WORKDIR is ${TMPDIR}/work... Not doing any check")
236 output = os.popen("%s -B -F%%r#F '%s'" % (scanelf,file))
237 txt = output.readline().split()
240 error_msg = "package %s contains bad RPATH %s in file %s" % (name, line, file)
241 sane = package_qa_handle_error(1, error_msg, name, file, d)
245 def package_qa_check_devdbg(path, name,d):
247 Check for debug remains inside the binary or
248 non dev packages containing
254 if not "-dev" in name:
255 if path[-3:] == ".so" and os.path.islink(path):
256 error_msg = "non -dev package contains symlink .so: %s path '%s'" % \
257 (name, package_qa_clean_path(path,d))
258 sane = package_qa_handle_error(0, error_msg, name, path, d)
260 if not "-dbg" in name:
262 error_msg = "non debug package contains .debug directory: %s path %s" % \
263 (name, package_qa_clean_path(path,d))
264 sane = package_qa_handle_error(3, error_msg, name, path, d)
268 def package_qa_check_perm(path,name,d):
270 Check the permission of files
275 def package_qa_check_arch(path,name,d):
277 Check if archs are compatible
281 target_os = bb.data.getVar('TARGET_OS', d, True)
282 target_arch = bb.data.getVar('TARGET_ARCH', d, True)
284 # FIXME: Cross package confuse this check, so just skip them
285 if bb.data.inherits_class('cross', d) or bb.data.inherits_class('sdk', d):
288 # avoid following links to /usr/bin (e.g. on udev builds)
289 # we will check the files pointed to anyway...
290 if os.path.islink(path):
293 #if this will throw an exception, then fix the dict above
294 (machine, osabi, abiversion, littleendian, bits32) \
295 = package_qa_get_machine_dict()[target_os][target_arch]
296 elf = package_qa_get_elf(path, bits32)
302 # Check the architecture and endiannes of the binary
303 if not machine == elf.machine():
304 error_msg = "Architecture did not match (%d to %d) on %s" % \
305 (machine, elf.machine(), package_qa_clean_path(path,d))
306 sane = package_qa_handle_error(4, error_msg, name, path, d)
307 elif not littleendian == elf.isLittleEndian():
308 error_msg = "Endiannes did not match (%d to %d) on %s" % \
309 (littleendian, elf.isLittleEndian(), package_qa_clean_path(path,d))
310 sane = package_qa_handle_error(4, error_msg, name, path, d)
314 def package_qa_check_desktop(path, name, d):
316 Run all desktop files through desktop-file-validate.
320 if path.endswith(".desktop"):
321 output = os.popen("desktop-file-validate %s" % path)
322 # This only produces output on errors
324 sane = package_qa_handle_error(7, l.strip(), name, path, d)
328 def package_qa_check_staged(path,d):
330 Check staged la and pc files for sanity
331 -e.g. installed being false
333 As this is run after every stage we should be able
334 to find the one responsible for the errors easily even
335 if we look at every .pc and .la file
340 tmpdir = bb.data.getVar('TMPDIR', d, True)
341 workdir = os.path.join(tmpdir, "work")
343 installed = "installed=yes"
344 if bb.data.inherits_class("native", d) or bb.data.inherits_class("cross", d):
345 pkgconfigcheck = workdir
347 pkgconfigcheck = tmpdir
349 # find all .la and .pc files
351 # and check for stuff that looks wrong
352 for root, dirs, files in os.walk(path):
354 path = os.path.join(root,file)
355 if file[-2:] == "la":
356 file_content = open(path).read()
357 # Don't check installed status for native/cross packages
358 if not bb.data.inherits_class("native", d) and not bb.data.inherits_class("cross", d):
359 if installed in file_content:
360 error_msg = "%s failed sanity test (installed) in path %s" % (file,root)
361 sane = package_qa_handle_error(5, error_msg, "staging", path, d)
362 if workdir in file_content:
363 error_msg = "%s failed sanity test (workdir) in path %s" % (file,root)
364 sane = package_qa_handle_error(8, error_msg, "staging", path, d)
365 elif file[-2:] == "pc":
366 file_content = open(path).read()
367 if pkgconfigcheck in file_content:
368 error_msg = "%s failed sanity test (tmpdir) in path %s" % (file,root)
369 sane = package_qa_handle_error(6, error_msg, "staging", path, d)
373 # Walk over all files in a directory and call func
374 def package_qa_walk(path, funcs, package,d):
378 for root, dirs, files in os.walk(path):
380 path = os.path.join(root,file)
382 if not func(path, package,d):
387 def package_qa_check_rdepends(pkg, workdir, d):
390 if not "-dbg" in pkg and not "task-" in pkg and not "-image" in pkg:
391 # Copied from package_ipk.bbclass
392 # boiler plate to update the data
393 localdata = bb.data.createCopy(d)
394 root = "%s/install/%s" % (workdir, pkg)
396 bb.data.setVar('ROOT', '', localdata)
397 bb.data.setVar('ROOT_%s' % pkg, root, localdata)
398 pkgname = bb.data.getVar('PKG_%s' % pkg, localdata, True)
401 bb.data.setVar('PKG', pkgname, localdata)
403 overrides = bb.data.getVar('OVERRIDES', localdata)
405 raise bb.build.FuncFailed('OVERRIDES not defined')
406 overrides = bb.data.expand(overrides, localdata)
407 bb.data.setVar('OVERRIDES', overrides + ':' + pkg, localdata)
409 bb.data.update_data(localdata)
411 # Now check the RDEPENDS
412 rdepends = explode_deps(bb.data.getVar('RDEPENDS', localdata, True) or "")
415 # Now do the sanity check!!!
416 for rdepend in rdepends:
417 if "-dbg" in rdepend:
418 error_msg = "%s rdepends on %s" % (pkgname,rdepend)
419 sane = package_qa_handle_error(2, error_msg, pkgname, rdepend, d)
423 # The PACKAGE FUNC to scan each package
424 python do_package_qa () {
425 bb.note("DO PACKAGE QA")
426 workdir = bb.data.getVar('WORKDIR', d, True)
427 packages = bb.data.getVar('PACKAGES',d, True)
429 # no packages should be scanned
433 checks = [package_qa_check_rpath, package_qa_check_devdbg,
434 package_qa_check_perm, package_qa_check_arch,
435 package_qa_check_desktop]
438 for package in packages.split():
439 if bb.data.getVar('INSANE_SKIP_' + package, d, True):
440 bb.note("Package: %s (skipped)" % package)
443 bb.note("Checking Package: %s" % package)
444 path = "%s/install/%s" % (workdir, package)
445 if not package_qa_walk(path, checks, package, d):
447 if not package_qa_check_rdepends(package, workdir, d):
448 rdepends_sane = False
450 if not walk_sane or not rdepends_sane:
451 bb.fatal("QA run found fatal errors. Please consider fixing them.")
452 bb.note("DONE with PACKAGE QA")
456 # The Staging Func, to check all staging
457 addtask qa_staging after do_populate_staging before do_build
458 python do_qa_staging() {
459 bb.note("QA checking staging")
461 if not package_qa_check_staged(bb.data.getVar('STAGING_LIBDIR',d,True), d):
462 bb.fatal("QA staging was broken by the package built above")
465 # Check broken config.log files
466 addtask qa_configure after do_configure before do_compile
467 python do_qa_configure() {
468 bb.note("Checking sanity of the config.log file")
470 for root, dirs, files in os.walk(bb.data.getVar('WORKDIR', d, True)):
471 statement = "grep 'CROSS COMPILE Badness:' %s > /dev/null" % \
472 os.path.join(root,"config.log")
473 if "config.log" in files:
474 if os.system(statement) == 0:
475 bb.fatal("""This autoconf log indicates errors, it looked at host includes.
476 Rerun configure task after fixing this. The path was '%s'""" % root)