llvm-common: use do_install to do the stuff
[openembedded.git] / classes / insane.bbclass
blobb8743d19183102a9334f518d3de519cbb4c2c05f
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.
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
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.
21 inherit package
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():
33     return {
34             "darwin9" : { 
35                         "arm" :       (   40,     0,    0,          True,          True),
36                       },
37             "linux" : { 
38                         "arm" :       (   40,    97,    0,          True,          True),
39                         "armeb":      (   40,    97,    0,          False,         True),
40                         "i386":       (    3,     0,    0,          True,          True),
41                         "i486":       (    3,     0,    0,          True,          True),
42                         "i586":       (    3,     0,    0,          True,          True),
43                         "i686":       (    3,     0,    0,          True,          True),
44                         "x86_64":     (   62,     0,    0,          True,          False),
45                         "ia64":       (   50,     0,    0,          True,          False),
46                         "alpha":      (36902,     0,    0,          True,          False),
47                         "hppa":       (   15,     3,    0,          False,         True),
48                         "m68k":       (    4,     0,    0,          False,         True),
49                         "mips":       (    8,     0,    0,          False,         True),
50                         "mipsel":     (    8,     0,    0,          True,          True),
51                         "nios2":      (  113,     0,    0,          True,          True),
52                         "powerpc":    (   20,     0,    0,          False,         True),
53                         "s390":       (   22,     0,    0,          False,         True),
54                         "sh4":        (   42,     0,    0,          True,          True),
55                         "sparc":      (    2,     0,    0,          False,         True),
56                       },
57             "linux-uclibc" : { 
58                         "arm" :       (   40,    97,    0,          True,          True),
59                         "armeb":      (   40,    97,    0,          False,         True),
60                         "avr32":      ( 6317,     0,    0,          False,         True),
61                         "i386":       (    3,     0,    0,          True,          True),
62                         "i486":       (    3,     0,    0,          True,          True),
63                         "i586":       (    3,     0,    0,          True,          True),
64                         "i686":       (    3,     0,    0,          True,          True),
65                         "x86_64":     (   62,     0,    0,          True,          False),
66                         "mips":       (    8,     0,    0,          False,         True),
67                         "mipsel":     (    8,     0,    0,          True,          True),
68                         "nios2":      (  113,     0,    0,          True,          True),
69                         "powerpc":    (   20,     0,    0,          False,         True),
70                         "sh4":        (   42,     0,    0,          True,          True),
71                       },
72             "uclinux-uclibc" : {
73                         "bfin":       (  106,     0,    0,          True,         True),
74                       }, 
75             "linux-gnueabi" : {
76                         "arm" :       (   40,     0,    0,          True,          True),
77                         "armeb" :     (   40,     0,    0,          False,         True),
78                       },
79             "linux-uclibceabi" : {
80                         "arm" :       (   40,     0,    0,          True,          True),
81                         "armeb" :     (   40,     0,    0,          False,         True),
82                       },
83             "linux-gnuspe" : {
84                         "powerpc":    (   20,     0,    0,          False,         True),
85                       },
86             "linux-uclibcspe" : {
87                         "powerpc":    (   20,     0,    0,          False,         True),
88                       },
90        }
93 # Known Error classes
94 # 0 - non dev contains .so
95 # 1 - package contains a dangerous RPATH
96 # 2 - package depends on debug package
97 # 3 - non dbg contains .so
98 # 4 - wrong architecture
99 # 5 - .la contains installed=yes or reference to the workdir
100 # 6 - .pc contains reference to /usr/include or workdir
101 # 7 - the desktop file is not valid
102 # 8 - .la contains reference to the workdir
103 # 9 - LDFLAGS ignored
105 def package_qa_clean_path(path,d):
106     """ Remove the common prefix from the path. In this case it is the TMPDIR"""
107     return path.replace(bb.data.getVar('TMPDIR',d,True),"")
109 def package_qa_make_fatal_error(error_class, name, path,d):
110     """
111     decide if an error is fatal
113     TODO: Load a whitelist of known errors
114     """
115     return not error_class in [0, 5, 7]
117 def package_qa_write_error(error_class, name, path, d):
118     """
119     Log the error
120     """
121     if not bb.data.getVar('QA_LOG', d):
122         bb.note("a QA error occured but will not be logged because QA_LOG is not set")
123         return
125     ERROR_NAMES =[
126         "non dev contains .so",
127         "package contains RPATH",
128         "package depends on debug package",
129         "non dbg contains .debug",
130         "wrong architecture",
131         "evil hides inside the .la",
132         "evil hides inside the .pc",
133         "the desktop file is not valid",
134         ".la contains reference to the workdir",
135         "LDFLAGS ignored",
136     ]
138     log_path = os.path.join( bb.data.getVar('T', d, True), "log.qa_package" )
139     f = file( log_path, "a+")
140     print >> f, "%s, %s, %s" % \
141              (ERROR_NAMES[error_class], name, package_qa_clean_path(path,d))
142     f.close()
144 def package_qa_handle_error(error_class, error_msg, name, path, d):
145     bb.error("QA Issue with %s: %s" % (name, error_msg))
146     package_qa_write_error(error_class, name, path, d)
147     return not package_qa_make_fatal_error(error_class, name, path, d)
149 def package_qa_check_rpath(file,name,d, elf):
150     """
151     Check for dangerous RPATHs
152     """
153     if not elf:
154         return True
156     import bb, os
157     sane = True
158     scanelf = os.path.join(bb.data.getVar('STAGING_BINDIR_NATIVE',d,True),'scanelf')
159     bad_dir = bb.data.getVar('TMPDIR', d, True) + "/work"
160     bad_dir_test = bb.data.getVar('TMPDIR', d, True)
161     if not os.path.exists(scanelf):
162         bb.fatal("Can not check RPATH, scanelf (part of pax-utils-native) not found")
164     if not bad_dir in bb.data.getVar('WORKDIR', d, True):
165         bb.fatal("This class assumed that WORKDIR is ${TMPDIR}/work... Not doing any check")
167     output = os.popen("%s -B -F%%r#F '%s'" % (scanelf,file))
168     txt    = output.readline().split()
169     for line in txt:
170         if bad_dir in line:
171             error_msg = "package %s contains bad RPATH %s in file %s" % (name, line, file)
172             sane = package_qa_handle_error(1, error_msg, name, file, d)
174     return sane
176 def package_qa_check_dev(path, name,d, elf):
177     """
178     Check for ".so" library symlinks in non-dev packages
179     """
181     sane = True
183     # SDK packages are special.
184     for s in ['sdk', 'canadian-sdk']:
185         if bb.data.inherits_class(s, d):
186             return True
188     if not name.endswith("-dev") and path.endswith(".so") and os.path.islink(path):
189         error_msg = "non -dev package contains symlink .so: %s path '%s'" % \
190                  (name, package_qa_clean_path(path,d))
191         sane = package_qa_handle_error(0, error_msg, name, path, d)
193     return sane
195 def package_qa_check_dbg(path, name,d, elf):
196     """
197     Check for ".debug" files or directories outside of the dbg package
198     """
200     sane = True
202     if not "-dbg" in name:
203         if '.debug' in path.split(os.path.sep):
204             error_msg = "non debug package contains .debug directory: %s path %s" % \
205                      (name, package_qa_clean_path(path,d))
206             sane = package_qa_handle_error(3, error_msg, name, path, d)
208     return sane
210 def package_qa_check_perm(path,name,d, elf):
211     """
212     Check the permission of files
213     """
214     sane = True
215     return sane
217 def package_qa_check_arch(path,name,d, elf):
218     """
219     Check if archs are compatible
220     """
221     if not elf:
222         return True
224     sane = True
225     target_os   = bb.data.getVar('TARGET_OS',   d, True)
226     target_arch = bb.data.getVar('TARGET_ARCH', d, True)
228     # FIXME: Cross package confuse this check, so just skip them
229     for s in ['cross', 'sdk', 'canadian-cross', 'canadian-sdk']:
230         if bb.data.inherits_class(s, d):
231             return True
233     # avoid following links to /usr/bin (e.g. on udev builds)
234     # we will check the files pointed to anyway...
235     if os.path.islink(path):
236         return True
238     #if this will throw an exception, then fix the dict above
239     (machine, osabi, abiversion, littleendian, bits32) \
240         = package_qa_get_machine_dict()[target_os][target_arch]
242     # Check the architecture and endiannes of the binary
243     if not machine == elf.machine():
244         error_msg = "Architecture did not match (%d to %d) on %s" % \
245                  (machine, elf.machine(), package_qa_clean_path(path,d))
246         sane = package_qa_handle_error(4, error_msg, name, path, d)
247     elif not littleendian == elf.isLittleEndian():
248         error_msg = "Endiannes did not match (%d to %d) on %s" % \
249                  (littleendian, elf.isLittleEndian(), package_qa_clean_path(path,d))
250         sane = package_qa_handle_error(4, error_msg, name, path, d)
252     return sane
254 def package_qa_check_desktop(path, name, d, elf):
255     """
256     Run all desktop files through desktop-file-validate.
257     """
258     sane = True
259     env_path = bb.data.getVar('PATH', d, True)
261     if path.endswith(".desktop"):
262         output = os.popen("PATH=%s desktop-file-validate %s" % (env_path, path))
263         # This only produces output on errors
264         for l in output:
265             sane = package_qa_handle_error(7, l.strip(), name, path, d)
267     return sane
269 def package_qa_hash_style(path, name, d, elf):
270     """
271     Check if the binary has the right hash style...
272     """
274     if not elf:
275         return True
277     if os.path.islink(path):
278         return True
280     gnu_hash = "--hash-style=gnu" in bb.data.getVar('LDFLAGS', d, True)
281     if not gnu_hash:
282         gnu_hash = "--hash-style=both" in bb.data.getVar('LDFLAGS', d, True)
283     if not gnu_hash:
284         return True
286     objdump = bb.data.getVar('OBJDUMP', d, True)
287     env_path = bb.data.getVar('PATH', d, True)
289     sane = True
290     elf = False
291     # A bit hacky. We do not know if path is an elf binary or not
292     # we will search for 'NEEDED' or 'INIT' as this should be printed...
293     # and come before the HASH section (guess!!!) and works on split out
294     # debug symbols too
295     for line in os.popen("LC_ALL=C PATH=%s %s -p '%s' 2> /dev/null" % (env_path, objdump, path), "r"):
296         if "NEEDED" in line or "INIT" in line:
297             sane = False
298             elf = True
299         if "GNU_HASH" in line:
300             sane = True
301         if "[mips32]" in line or "[mips64]" in line:
302             sane = True
304     if elf and not sane:
305         error_msg = "No GNU_HASH in the elf binary: '%s'" % path
306         return package_qa_handle_error(9, error_msg, name, path, d)
308     return True
310 def package_qa_check_staged(path,d):
311     """
312     Check staged la and pc files for sanity
313       -e.g. installed being false
315         As this is run after every stage we should be able
316         to find the one responsible for the errors easily even
317         if we look at every .pc and .la file
318     """
320     sane = True
321     tmpdir = bb.data.getVar('TMPDIR', d, True)
322     workdir = os.path.join(tmpdir, "work")
324     installed = "installed=yes"
325     iscrossnative = False
326     pkgconfigcheck = tmpdir
327     for s in ['cross', 'native', 'canadian-cross', 'canadian-native']:
328         if bb.data.inherits_class(s, d):
329             pkgconfigcheck = workdir
330             iscrossnative = True
332     # find all .la and .pc files
333     # read the content
334     # and check for stuff that looks wrong
335     for root, dirs, files in os.walk(path):
336         for file in files:
337             path = os.path.join(root,file)
338             if file.endswith(".la"):
339                 file_content = open(path).read()
340                 # Don't check installed status for native/cross packages
341                 if not iscrossnative:
342                     if installed in file_content:
343                         error_msg = "%s failed sanity test (installed) in path %s" % (file,root)
344                         sane = package_qa_handle_error(5, error_msg, "staging", path, d)
345                 if workdir in file_content:
346                     error_msg = "%s failed sanity test (workdir) in path %s" % (file,root)
347                     sane = package_qa_handle_error(8, error_msg, "staging", path, d)
348             elif file.endswith(".pc"):
349                 file_content = open(path).read()
350                 if pkgconfigcheck in file_content:
351                     error_msg = "%s failed sanity test (tmpdir) in path %s" % (file,root)
352                     sane = package_qa_handle_error(6, error_msg, "staging", path, d)
354     return sane
356 # Walk over all files in a directory and call func
357 def package_qa_walk(path, funcs, package,d):
358     import oe.qa
360     #if this will throw an exception, then fix the dict above
361     target_os   = bb.data.getVar('TARGET_OS',   d, True)
362     target_arch = bb.data.getVar('TARGET_ARCH', d, True)
363     (machine, osabi, abiversion, littleendian, bits32) \
364         = package_qa_get_machine_dict()[target_os][target_arch]
366     sane = True
367     for root, dirs, files in os.walk(path):
368         for file in files:
369             path = os.path.join(root,file)
370             elf = oe.qa.ELFFile(path, bits32)
371             try:
372                 elf.open()
373             except:
374                 elf = None
375             for func in funcs:
376                 if not func(path, package,d, elf):
377                     sane = False
379     return sane
381 def package_qa_check_rdepends(pkg, pkgdest, d):
382     sane = True
383     if not "-dbg" in pkg and not "task-" in pkg and not "-image" in pkg:
384         # Copied from package_ipk.bbclass
385         # boiler plate to update the data
386         localdata = bb.data.createCopy(d)
387         root = "%s/%s" % (pkgdest, pkg)
389         bb.data.setVar('ROOT', '', localdata) 
390         bb.data.setVar('ROOT_%s' % pkg, root, localdata)
391         pkgname = bb.data.getVar('PKG_%s' % pkg, localdata, True)
392         if not pkgname:
393             pkgname = pkg
394         bb.data.setVar('PKG', pkgname, localdata)
396         overrides = bb.data.getVar('OVERRIDES', localdata)
397         if not overrides:
398             raise bb.build.FuncFailed('OVERRIDES not defined')
399         overrides = bb.data.expand(overrides, localdata)
400         bb.data.setVar('OVERRIDES', overrides + ':' + pkg, localdata)
402         bb.data.update_data(localdata)
404         # Now check the RDEPENDS
405         rdepends = explode_deps(bb.data.getVar('RDEPENDS', localdata, True) or "")
408         # Now do the sanity check!!!
409         for rdepend in rdepends:
410             if "-dbg" in rdepend:
411                 error_msg = "%s rdepends on %s" % (pkgname,rdepend)
412                 sane = package_qa_handle_error(2, error_msg, pkgname, rdepend, d)
414     return sane
416 # The PACKAGE FUNC to scan each package
417 python do_package_qa () {
418     bb.debug(2, "DO PACKAGE QA")
419     pkgdest = bb.data.getVar('PKGDEST', d, True)
420     packages = bb.data.getVar('PACKAGES',d, True)
422     # no packages should be scanned
423     if not packages:
424         return
426     checks = [package_qa_check_rpath, package_qa_check_dev,
427               package_qa_check_perm, package_qa_check_arch,
428               package_qa_check_desktop, package_qa_hash_style,
429               package_qa_check_dbg]
430     walk_sane = True
431     rdepends_sane = True
432     for package in packages.split():
433         if bb.data.getVar('INSANE_SKIP_' + package, d, True):
434             bb.note("package %s skipped" % package)
435             continue
437         bb.debug(1, "Checking Package: %s" % package)
438         path = "%s/%s" % (pkgdest, package)
439         if not package_qa_walk(path, checks, package, d):
440             walk_sane  = False
441         if not package_qa_check_rdepends(package, pkgdest, d):
442             rdepends_sane = False
444     if not walk_sane or not rdepends_sane:
445         bb.fatal("QA run found fatal errors. Please consider fixing them.")
446     bb.debug(2, "DONE with PACKAGE QA")
450 # The Staging Func, to check all staging
451 addtask qa_staging after do_populate_sysroot before do_build
452 python do_qa_staging() {
453     bb.debug(2, "QA checking staging")
455     if not package_qa_check_staged(bb.data.getVar('STAGING_LIBDIR',d,True), d):
456         bb.fatal("QA staging was broken by the package built above")
459 # Check broken config.log files
460 addtask qa_configure after do_configure before do_compile
461 python do_qa_configure() {
462     configs = []
463     bb.debug(1, "Checking sanity of the config.log file")
464     for root, dirs, files in os.walk(bb.data.getVar('WORKDIR', d, True)):
465         statement = "grep 'CROSS COMPILE Badness:' %s > /dev/null" % \
466                     os.path.join(root,"config.log")
467         if "config.log" in files:
468             if os.system(statement) == 0:
469                 bb.fatal("""This autoconf log indicates errors, it looked at host includes.
470 Rerun configure task after fixing this. The path was '%s'""" % root)
472         if "configure.ac" in files:
473             configs.append(os.path.join(root,"configure.ac"))
474         if "configure.in" in files:
475             configs.append(os.path.join(root, "configure.in"))
477     if "gettext" not in bb.data.getVar('P', d, True):
478        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):
479           gt = "gettext-native"
480        elif bb.data.inherits_class('cross-canadian', d):
481           gt = "gettext-nativesdk"
482        else:
483           gt = "gettext"
484        deps = bb.utils.explode_deps(bb.data.getVar('DEPENDS', d, True) or "")
485        if gt not in deps:
486           for config in configs:
487               gnu = "grep \"^[[:space:]]*AM_GNU_GETTEXT\" %s >/dev/null" % config
488               if os.system(gnu) == 0:
489                  bb.note("""Gettext required but not in DEPENDS for file %s.
490 Missing inherit gettext?""" % config)