xfce4-battery-plugin 0.5.1: Add xfce4-battery-plugin recipe with APM/ACPI support.
[openembedded.git] / classes / insane.bbclass
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
15
16 #
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.
20 #
21 inherit package
22 PACKAGE_DEPENDS += "pax-utils-native desktop-file-utils-native"
23 PACKAGEFUNCS += " do_package_qa "
24
25
26 #
27 # dictionary for elf headers
28 #
29 # feel free to add and correct.
30 #
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                         "powerpc":    (20,     0,    0,          False,         True),
41                         "i386":       ( 3,     0,    0,          True,          True),
42                         "i486":       ( 3,     0,    0,          True,          True),
43                         "i586":       ( 3,     0,    0,          True,          True),
44                         "i686":       ( 3,     0,    0,          True,          True),
45                         "x86_64":     (62,     0,    0,          True,          False),
46                         "ia64":       (50,     0,    0,          True,          False),
47                         "alpha":      (36902,  0,    0,          True,          False),
48                         "hppa":       (15,     3,    0,          False,         True),
49                         "m68k":       ( 4,     0,    0,          False,         True),
50                         "mips":       ( 8,     0,    0,          False,         True),
51                         "mipsel":     ( 8,     0,    0,          True,          True),
52                         "s390":       (22,     0,    0,          False,         True),
53                         "sh4":        (42,     0,    0,          True,          True),
54                         "sparc":      ( 2,     0,    0,          False,         True),
55                       },
56             "linux-uclibc" : { 
57                         "arm" :       (  40,    97,    0,          True,          True),
58                         "armeb":      (  40,    97,    0,          False,         True),
59                         "powerpc":    (  20,     0,    0,          False,         True),
60                         "i386":       (   3,     0,    0,          True,          True),
61                         "i486":       (   3,     0,    0,          True,          True),
62                         "i586":       (   3,     0,    0,          True,          True),
63                         "i686":       (   3,     0,    0,          True,          True),
64                         "mips":       (   8,     0,    0,          False,         True),
65                         "mipsel":     (   8,     0,    0,          True,          True),
66                         "avr32":      (6317,     0,    0,          False,         True),
67                         "sh4":        (42,       0,    0,          True,          True),
68
69                       },
70             "uclinux-uclibc" : {
71                         "bfin":       ( 106,     0,    0,          True,         True),
72                       }, 
73             "linux-gnueabi" : {
74                         "arm" :       (40,     0,    0,          True,          True),
75                         "armeb" :     (40,     0,    0,          False,         True),
76                       },
77             "linux-uclibceabi" : {
78                         "arm" :       (40,     0,    0,          True,          True),
79                         "armeb" :     (40,     0,    0,          False,         True),
80                       },
81             "linux-gnuspe" : {
82                         "powerpc":    (20,     0,    0,          False,         True),
83                       },
84             "linux-uclibcspe" : {
85                         "powerpc":    (20,     0,    0,          False,         True),
86                       },
87
88        }
89
90 # factory for a class, embedded in a method
91 def package_qa_get_elf(path, bits32):
92     class ELFFile:
93         EI_NIDENT = 16
94
95         EI_CLASS      = 4
96         EI_DATA       = 5
97         EI_VERSION    = 6
98         EI_OSABI      = 7
99         EI_ABIVERSION = 8
100
101         # possible values for EI_CLASS
102         ELFCLASSNONE = 0
103         ELFCLASS32   = 1
104         ELFCLASS64   = 2
105
106         # possible value for EI_VERSION
107         EV_CURRENT   = 1
108
109         # possible values for EI_DATA
110         ELFDATANONE  = 0
111         ELFDATA2LSB  = 1
112         ELFDATA2MSB  = 2
113
114         def my_assert(self, expectation, result):
115             if not expectation == result:
116                 #print "'%x','%x' %s" % (ord(expectation), ord(result), self.name)
117                 raise Exception("This does not work as expected")
118
119         def __init__(self, name):
120             self.name = name
121
122         def open(self):
123             self.file = file(self.name, "r")
124             self.data = self.file.read(ELFFile.EI_NIDENT+4)
125
126             self.my_assert(len(self.data), ELFFile.EI_NIDENT+4)
127             self.my_assert(self.data[0], chr(0x7f) )
128             self.my_assert(self.data[1], 'E')
129             self.my_assert(self.data[2], 'L')
130             self.my_assert(self.data[3], 'F')
131             if bits32 :
132                 self.my_assert(self.data[ELFFile.EI_CLASS], chr(ELFFile.ELFCLASS32))
133             else:
134                 self.my_assert(self.data[ELFFile.EI_CLASS], chr(ELFFile.ELFCLASS64))
135             self.my_assert(self.data[ELFFile.EI_VERSION], chr(ELFFile.EV_CURRENT) )
136
137             self.sex = self.data[ELFFile.EI_DATA]
138             if self.sex == chr(ELFFile.ELFDATANONE):
139                 raise Exception("self.sex == ELFDATANONE")
140             elif self.sex == chr(ELFFile.ELFDATA2LSB):
141                 self.sex = "<"
142             elif self.sex == chr(ELFFile.ELFDATA2MSB):
143                 self.sex = ">"
144             else:
145                 raise Exception("Unknown self.sex")
146
147         def osAbi(self):
148             return ord(self.data[ELFFile.EI_OSABI])
149
150         def abiVersion(self):
151             return ord(self.data[ELFFile.EI_ABIVERSION])
152
153         def isLittleEndian(self):
154             return self.sex == "<"
155
156         def isBigEngian(self):
157             return self.sex == ">"
158
159         def machine(self):
160             """
161             We know the sex stored in self.sex and we
162             know the position
163             """
164             import struct
165             (a,) = struct.unpack(self.sex+"H", self.data[18:20])
166             return a
167
168     return ELFFile(path)
169
170
171 # Known Error classes
172 # 0 - non dev contains .so
173 # 1 - package contains a dangerous RPATH
174 # 2 - package depends on debug package
175 # 3 - non dbg contains .so
176 # 4 - wrong architecture
177 # 5 - .la contains installed=yes or reference to the workdir
178 # 6 - .pc contains reference to /usr/include or workdir
179 # 7 - the desktop file is not valid
180 # 8 - .la contains reference to the workdir
181 # 9 - LDFLAGS ignored
182
183 def package_qa_clean_path(path,d):
184     """ Remove the common prefix from the path. In this case it is the TMPDIR"""
185     return path.replace(bb.data.getVar('TMPDIR',d,True),"")
186
187 def package_qa_make_fatal_error(error_class, name, path,d):
188     """
189     decide if an error is fatal
190
191     TODO: Load a whitelist of known errors
192     """
193     return not error_class in [0, 5, 7]
194
195 def package_qa_write_error(error_class, name, path, d):
196     """
197     Log the error
198     """
199     if not bb.data.getVar('QA_LOG', d):
200         bb.note("a QA error occured but will not be logged because QA_LOG is not set")
201         return
202
203     ERROR_NAMES =[
204         "non dev contains .so",
205         "package contains RPATH",
206         "package depends on debug package",
207         "non dbg contains .debug",
208         "wrong architecture",
209         "evil hides inside the .la",
210         "evil hides inside the .pc",
211         "the desktop file is not valid",
212         ".la contains reference to the workdir",
213         "LDFLAGS ignored",
214     ]
215
216     log_path = os.path.join( bb.data.getVar('T', d, True), "log.qa_package" )
217     f = file( log_path, "a+")
218     print >> f, "%s, %s, %s" % \
219              (ERROR_NAMES[error_class], name, package_qa_clean_path(path,d))
220     f.close()
221
222 def package_qa_handle_error(error_class, error_msg, name, path, d):
223     bb.error("QA Issue with %s: %s" % (name, error_msg))
224     package_qa_write_error(error_class, name, path, d)
225     return not package_qa_make_fatal_error(error_class, name, path, d)
226
227 def package_qa_check_rpath(file,name,d, elf):
228     """
229     Check for dangerous RPATHs
230     """
231     if not elf:
232         return True
233
234     import bb, os
235     sane = True
236     scanelf = os.path.join(bb.data.getVar('STAGING_BINDIR_NATIVE',d,True),'scanelf')
237     bad_dir = bb.data.getVar('TMPDIR', d, True) + "/work"
238     bad_dir_test = bb.data.getVar('TMPDIR', d, True)
239     if not os.path.exists(scanelf):
240         bb.fatal("Can not check RPATH, scanelf (part of pax-utils-native) not found")
241
242     if not bad_dir in bb.data.getVar('WORKDIR', d, True):
243         bb.fatal("This class assumed that WORKDIR is ${TMPDIR}/work... Not doing any check")
244
245     output = os.popen("%s -B -F%%r#F '%s'" % (scanelf,file))
246     txt    = output.readline().split()
247     for line in txt:
248         if bad_dir in line:
249             error_msg = "package %s contains bad RPATH %s in file %s" % (name, line, file)
250             sane = package_qa_handle_error(1, error_msg, name, file, d)
251
252     return sane
253
254 def package_qa_check_dev(path, name,d, elf):
255     """
256     Check for ".so" library symlinks in non-dev packages
257     """
258
259     sane = True
260
261     # SDK packages are special.
262     for s in ['sdk', 'canadian-sdk']:
263         if bb.data.inherits_class(s, d):
264             return True
265
266     if not name.endswith("-dev") and path.endswith(".so") and os.path.islink(path):
267         error_msg = "non -dev package contains symlink .so: %s path '%s'" % \
268                  (name, package_qa_clean_path(path,d))
269         sane = package_qa_handle_error(0, error_msg, name, path, d)
270
271     return sane
272
273 def package_qa_check_dbg(path, name,d, elf):
274     """
275     Check for ".debug" files or directories outside of the dbg package
276     """
277
278     sane = True
279
280     if not "-dbg" in name:
281         if '.debug' in path.split(os.path.sep):
282             error_msg = "non debug package contains .debug directory: %s path %s" % \
283                      (name, package_qa_clean_path(path,d))
284             sane = package_qa_handle_error(3, error_msg, name, path, d)
285
286     return sane
287
288 def package_qa_check_perm(path,name,d, elf):
289     """
290     Check the permission of files
291     """
292     sane = True
293     return sane
294
295 def package_qa_check_arch(path,name,d, elf):
296     """
297     Check if archs are compatible
298     """
299     if not elf:
300         return True
301
302     sane = True
303     target_os   = bb.data.getVar('TARGET_OS',   d, True)
304     target_arch = bb.data.getVar('TARGET_ARCH', d, True)
305
306     # FIXME: Cross package confuse this check, so just skip them
307     for s in ['cross', 'sdk', 'canadian-cross', 'canadian-sdk']:
308         if bb.data.inherits_class(s, d):
309             return True
310
311     # avoid following links to /usr/bin (e.g. on udev builds)
312     # we will check the files pointed to anyway...
313     if os.path.islink(path):
314         return True
315
316     #if this will throw an exception, then fix the dict above
317     (machine, osabi, abiversion, littleendian, bits32) \
318         = package_qa_get_machine_dict()[target_os][target_arch]
319
320     # Check the architecture and endiannes of the binary
321     if not machine == elf.machine():
322         error_msg = "Architecture did not match (%d to %d) on %s" % \
323                  (machine, elf.machine(), package_qa_clean_path(path,d))
324         sane = package_qa_handle_error(4, error_msg, name, path, d)
325     elif not littleendian == elf.isLittleEndian():
326         error_msg = "Endiannes did not match (%d to %d) on %s" % \
327                  (littleendian, elf.isLittleEndian(), package_qa_clean_path(path,d))
328         sane = package_qa_handle_error(4, error_msg, name, path, d)
329
330     return sane
331
332 def package_qa_check_desktop(path, name, d, elf):
333     """
334     Run all desktop files through desktop-file-validate.
335     """
336     sane = True
337     if path.endswith(".desktop"):
338         output = os.popen("desktop-file-validate %s" % path)
339         # This only produces output on errors
340         for l in output:
341             sane = package_qa_handle_error(7, l.strip(), name, path, d)
342
343     return sane
344
345 def package_qa_hash_style(path, name, d, elf):
346     """
347     Check if the binary has the right hash style...
348     """
349
350     if not elf:
351         return True
352
353     if os.path.islink(path):
354         return True
355
356     gnu_hash = "--hash-style=gnu" in bb.data.getVar('LDFLAGS', d, True)
357     if not gnu_hash:
358         gnu_hash = "--hash-style=both" in bb.data.getVar('LDFLAGS', d, True)
359     if not gnu_hash:
360         return True
361
362     objdump = bb.data.getVar('OBJDUMP', d, True)
363     env_path = bb.data.getVar('PATH', d, True)
364
365     sane = True
366     elf = False
367     # A bit hacky. We do not know if path is an elf binary or not
368     # we will search for 'NEEDED' or 'INIT' as this should be printed...
369     # and come before the HASH section (guess!!!) and works on split out
370     # debug symbols too
371     for line in os.popen("LC_ALL=C PATH=%s %s -p '%s' 2> /dev/null" % (env_path, objdump, path), "r"):
372         if "NEEDED" in line or "INIT" in line:
373             sane = False
374             elf = True
375         if "GNU_HASH" in line:
376             sane = True
377         if "[mips32]" in line or "[mips64]" in line:
378             sane = True
379
380     if elf and not sane:
381         error_msg = "No GNU_HASH in the elf binary: '%s'" % path
382         return package_qa_handle_error(9, error_msg, name, path, d)
383
384     return True
385
386 def package_qa_check_staged(path,d):
387     """
388     Check staged la and pc files for sanity
389       -e.g. installed being false
390
391         As this is run after every stage we should be able
392         to find the one responsible for the errors easily even
393         if we look at every .pc and .la file
394     """
395
396     sane = True
397     tmpdir = bb.data.getVar('TMPDIR', d, True)
398     workdir = os.path.join(tmpdir, "work")
399
400     installed = "installed=yes"
401     iscrossnative = False
402     pkgconfigcheck = tmpdir
403     for s in ['cross', 'native', 'canadian-cross', 'canadian-native']:
404         if bb.data.inherits_class(s, d):
405             pkgconfigcheck = workdir
406             iscrossnative = True
407
408     # find all .la and .pc files
409     # read the content
410     # and check for stuff that looks wrong
411     for root, dirs, files in os.walk(path):
412         for file in files:
413             path = os.path.join(root,file)
414             if file.endswith(".la"):
415                 file_content = open(path).read()
416                 # Don't check installed status for native/cross packages
417                 if not iscrossnative:
418                     if installed in file_content:
419                         error_msg = "%s failed sanity test (installed) in path %s" % (file,root)
420                         sane = package_qa_handle_error(5, error_msg, "staging", path, d)
421                 if workdir in file_content:
422                     error_msg = "%s failed sanity test (workdir) in path %s" % (file,root)
423                     sane = package_qa_handle_error(8, error_msg, "staging", path, d)
424             elif file.endswith(".pc"):
425                 file_content = open(path).read()
426                 if pkgconfigcheck in file_content:
427                     error_msg = "%s failed sanity test (tmpdir) in path %s" % (file,root)
428                     sane = package_qa_handle_error(6, error_msg, "staging", path, d)
429
430     return sane
431
432 # Walk over all files in a directory and call func
433 def package_qa_walk(path, funcs, package,d):
434     sane = True
435
436     #if this will throw an exception, then fix the dict above
437     target_os   = bb.data.getVar('TARGET_OS',   d, True)
438     target_arch = bb.data.getVar('TARGET_ARCH', d, True)
439     (machine, osabi, abiversion, littleendian, bits32) \
440         = package_qa_get_machine_dict()[target_os][target_arch]
441
442     for root, dirs, files in os.walk(path):
443         for file in files:
444             path = os.path.join(root,file)
445             elf = package_qa_get_elf(path, bits32)
446             try:
447                 elf.open()
448             except:
449                 elf = None
450             for func in funcs:
451                 if not func(path, package,d, elf):
452                     sane = False
453
454     return sane
455
456 def package_qa_check_rdepends(pkg, pkgdest, d):
457     sane = True
458     if not "-dbg" in pkg and not "task-" in pkg and not "-image" in pkg:
459         # Copied from package_ipk.bbclass
460         # boiler plate to update the data
461         localdata = bb.data.createCopy(d)
462         root = "%s/%s" % (pkgdest, pkg)
463
464         bb.data.setVar('ROOT', '', localdata) 
465         bb.data.setVar('ROOT_%s' % pkg, root, localdata)
466         pkgname = bb.data.getVar('PKG_%s' % pkg, localdata, True)
467         if not pkgname:
468             pkgname = pkg
469         bb.data.setVar('PKG', pkgname, localdata)
470
471         overrides = bb.data.getVar('OVERRIDES', localdata)
472         if not overrides:
473             raise bb.build.FuncFailed('OVERRIDES not defined')
474         overrides = bb.data.expand(overrides, localdata)
475         bb.data.setVar('OVERRIDES', overrides + ':' + pkg, localdata)
476
477         bb.data.update_data(localdata)
478
479         # Now check the RDEPENDS
480         rdepends = explode_deps(bb.data.getVar('RDEPENDS', localdata, True) or "")
481
482
483         # Now do the sanity check!!!
484         for rdepend in rdepends:
485             if "-dbg" in rdepend:
486                 error_msg = "%s rdepends on %s" % (pkgname,rdepend)
487                 sane = package_qa_handle_error(2, error_msg, pkgname, rdepend, d)
488
489     return sane
490
491 # The PACKAGE FUNC to scan each package
492 python do_package_qa () {
493     bb.debug(2, "DO PACKAGE QA")
494     pkgdest = bb.data.getVar('PKGDEST', d, True)
495     packages = bb.data.getVar('PACKAGES',d, True)
496
497     # no packages should be scanned
498     if not packages:
499         return
500
501     checks = [package_qa_check_rpath, package_qa_check_dev,
502               package_qa_check_perm, package_qa_check_arch,
503               package_qa_check_desktop, package_qa_hash_style,
504               package_qa_check_dbg]
505     walk_sane = True
506     rdepends_sane = True
507     for package in packages.split():
508         if bb.data.getVar('INSANE_SKIP_' + package, d, True):
509             bb.note("package %s skipped" % package)
510             continue
511
512         bb.debug(1, "Checking Package: %s" % package)
513         path = "%s/%s" % (pkgdest, package)
514         if not package_qa_walk(path, checks, package, d):
515             walk_sane  = False
516         if not package_qa_check_rdepends(package, pkgdest, d):
517             rdepends_sane = False
518
519     if not walk_sane or not rdepends_sane:
520         bb.fatal("QA run found fatal errors. Please consider fixing them.")
521     bb.debug(2, "DONE with PACKAGE QA")
522 }
523
524
525 # The Staging Func, to check all staging
526 addtask qa_staging after do_populate_staging before do_build
527 python do_qa_staging() {
528     bb.debug(2, "QA checking staging")
529
530     if not package_qa_check_staged(bb.data.getVar('STAGING_LIBDIR',d,True), d):
531         bb.fatal("QA staging was broken by the package built above")
532 }
533
534 # Check broken config.log files
535 addtask qa_configure after do_configure before do_compile
536 python do_qa_configure() {
537     bb.debug(1, "Checking sanity of the config.log file")
538     for root, dirs, files in os.walk(bb.data.getVar('WORKDIR', d, True)):
539         statement = "grep 'CROSS COMPILE Badness:' %s > /dev/null" % \
540                     os.path.join(root,"config.log")
541         if "config.log" in files:
542             if os.system(statement) == 0:
543                 bb.fatal("""This autoconf log indicates errors, it looked at host includes.
544 Rerun configure task after fixing this. The path was '%s'""" % root)
545 }