1# 2# Copyright OpenEmbedded Contributors 3# 4# SPDX-License-Identifier: MIT 5# 6 7# BB Class inspired by ebuild.sh 8# 9# This class will test files after installation for certain 10# security issues and other kind of issues. 11# 12# Checks we do: 13# -Check the ownership and permissions 14# -Check the RUNTIME path for the $TMPDIR 15# -Check if .la files wrongly point to workdir 16# -Check if .pc files wrongly point to workdir 17# -Check if packages contains .debug directories or .so files 18# where they should be in -dev or -dbg 19# -Check if config.log contains traces to broken autoconf tests 20# -Check invalid characters (non-utf8) on some package metadata 21# -Ensure that binaries in base_[bindir|sbindir|libdir] do not link 22# into exec_prefix 23# -Check that scripts in base_[bindir|sbindir|libdir] do not reference 24# files under exec_prefix 25# -Check if the package name is upper case 26 27# Elect whether a given type of error is a warning or error, they may 28# have been set by other files. 29WARN_QA ?= " libdir xorg-driver-abi buildpaths \ 30 textrel incompatible-license files-invalid \ 31 infodir build-deps src-uri-bad symlink-to-sysroot multilib \ 32 invalid-packageconfig host-user-contaminated uppercase-pn \ 33 mime mime-xdg unlisted-pkg-lics unhandled-features-check \ 34 missing-update-alternatives native-last missing-ptest \ 35 license-exists license-no-generic license-syntax license-format \ 36 license-incompatible license-file-missing obsolete-license \ 37 32bit-time \ 38 " 39ERROR_QA ?= "dev-so debug-deps dev-deps debug-files arch pkgconfig la \ 40 perms dep-cmp pkgvarcheck perm-config perm-line perm-link \ 41 split-strip packages-list pkgv-undefined var-undefined \ 42 version-going-backwards expanded-d invalid-chars \ 43 license-checksum dev-elf file-rdeps configure-unsafe \ 44 configure-gettext perllocalpod shebang-size \ 45 already-stripped installed-vs-shipped ldflags compile-host-path \ 46 install-host-path pn-overrides unknown-configure-option \ 47 useless-rpaths rpaths staticdev empty-dirs \ 48 patch-fuzz \ 49 " 50# Add usrmerge QA check based on distro feature 51ERROR_QA:append = "${@bb.utils.contains('DISTRO_FEATURES', 'usrmerge', ' usrmerge', '', d)}" 52ERROR_QA:append:layer-core = " patch-status" 53WARN_QA:append:layer-core = " missing-metadata missing-maintainer" 54 55FAKEROOT_QA = "host-user-contaminated" 56FAKEROOT_QA[doc] = "QA tests which need to run under fakeroot. If any \ 57enabled tests are listed here, the do_package_qa task will run under fakeroot." 58 59ALL_QA = "${WARN_QA} ${ERROR_QA}" 60 61UNKNOWN_CONFIGURE_OPT_IGNORE ?= "--enable-nls --disable-nls --disable-silent-rules --disable-dependency-tracking --with-libtool-sysroot --disable-static" 62 63# This is a list of directories that are expected to be empty. 64QA_EMPTY_DIRS ?= " \ 65 /dev/pts \ 66 /media \ 67 /proc \ 68 /run \ 69 /tmp \ 70 ${localstatedir}/run \ 71 ${localstatedir}/volatile \ 72" 73# It is possible to specify why a directory is expected to be empty by defining 74# QA_EMPTY_DIRS_RECOMMENDATION:<path>, which will then be included in the error 75# message if the directory is not empty. If it is not specified for a directory, 76# then "but it is expected to be empty" will be used. 77 78def package_qa_clean_path(path, d, pkg=None): 79 """ 80 Remove redundant paths from the path for display. If pkg isn't set then 81 TMPDIR is stripped, otherwise PKGDEST/pkg is stripped. 82 """ 83 if pkg: 84 path = path.replace(os.path.join(d.getVar("PKGDEST"), pkg), "/") 85 return path.replace(d.getVar("TMPDIR"), "/").replace("//", "/") 86 87QAPATHTEST[shebang-size] = "package_qa_check_shebang_size" 88def package_qa_check_shebang_size(path, name, d, elf, messages): 89 import stat 90 if os.path.islink(path) or stat.S_ISFIFO(os.stat(path).st_mode) or elf: 91 return 92 93 try: 94 with open(path, 'rb') as f: 95 stanza = f.readline(130) 96 except IOError: 97 return 98 99 if stanza.startswith(b'#!'): 100 try: 101 stanza.decode("utf-8") 102 except UnicodeDecodeError: 103 #If it is not a text file, it is not a script 104 return 105 106 if len(stanza) > 129: 107 oe.qa.add_message(messages, "shebang-size", "%s: %s maximum shebang size exceeded, the maximum size is 128." % (name, package_qa_clean_path(path, d))) 108 return 109 110QAPATHTEST[libexec] = "package_qa_check_libexec" 111def package_qa_check_libexec(path,name, d, elf, messages): 112 113 # Skip the case where the default is explicitly /usr/libexec 114 libexec = d.getVar('libexecdir') 115 if libexec == "/usr/libexec": 116 return True 117 118 if 'libexec' in path.split(os.path.sep): 119 oe.qa.add_message(messages, "libexec", "%s: %s is using libexec please relocate to %s" % (name, package_qa_clean_path(path, d), libexec)) 120 return False 121 122 return True 123 124QAPATHTEST[rpaths] = "package_qa_check_rpath" 125def package_qa_check_rpath(file,name, d, elf, messages): 126 """ 127 Check for dangerous RPATHs 128 """ 129 if not elf: 130 return 131 132 if os.path.islink(file): 133 return 134 135 bad_dirs = [d.getVar('BASE_WORKDIR'), d.getVar('STAGING_DIR_TARGET')] 136 137 phdrs = elf.run_objdump("-p", d) 138 139 import re 140 rpath_re = re.compile(r"\s+RPATH\s+(.*)") 141 for line in phdrs.split("\n"): 142 m = rpath_re.match(line) 143 if m: 144 rpath = m.group(1) 145 for dir in bad_dirs: 146 if dir in rpath: 147 oe.qa.add_message(messages, "rpaths", "package %s contains bad RPATH %s in file %s" % (name, rpath, file)) 148 149QAPATHTEST[useless-rpaths] = "package_qa_check_useless_rpaths" 150def package_qa_check_useless_rpaths(file, name, d, elf, messages): 151 """ 152 Check for RPATHs that are useless but not dangerous 153 """ 154 def rpath_eq(a, b): 155 return os.path.normpath(a) == os.path.normpath(b) 156 157 if not elf: 158 return 159 160 if os.path.islink(file): 161 return 162 163 libdir = d.getVar("libdir") 164 base_libdir = d.getVar("base_libdir") 165 166 phdrs = elf.run_objdump("-p", d) 167 168 import re 169 rpath_re = re.compile(r"\s+RPATH\s+(.*)") 170 for line in phdrs.split("\n"): 171 m = rpath_re.match(line) 172 if m: 173 rpath = m.group(1) 174 if rpath_eq(rpath, libdir) or rpath_eq(rpath, base_libdir): 175 # The dynamic linker searches both these places anyway. There is no point in 176 # looking there again. 177 oe.qa.add_message(messages, "useless-rpaths", "%s: %s contains probably-redundant RPATH %s" % (name, package_qa_clean_path(file, d, name), rpath)) 178 179QAPATHTEST[dev-so] = "package_qa_check_dev" 180def package_qa_check_dev(path, name, d, elf, messages): 181 """ 182 Check for ".so" library symlinks in non-dev packages 183 """ 184 185 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): 186 oe.qa.add_message(messages, "dev-so", "non -dev/-dbg/nativesdk- package %s contains symlink .so '%s'" % \ 187 (name, package_qa_clean_path(path, d, name))) 188 189QAPATHTEST[dev-elf] = "package_qa_check_dev_elf" 190def package_qa_check_dev_elf(path, name, d, elf, messages): 191 """ 192 Check that -dev doesn't contain real shared libraries. The test has to 193 check that the file is not a link and is an ELF object as some recipes 194 install link-time .so files that are linker scripts. 195 """ 196 if name.endswith("-dev") and path.endswith(".so") and not os.path.islink(path) and elf: 197 oe.qa.add_message(messages, "dev-elf", "-dev package %s contains non-symlink .so '%s'" % \ 198 (name, package_qa_clean_path(path, d, name))) 199 200QAPATHTEST[staticdev] = "package_qa_check_staticdev" 201def package_qa_check_staticdev(path, name, d, elf, messages): 202 """ 203 Check for ".a" library in non-staticdev packages 204 There are a number of exceptions to this rule, -pic packages can contain 205 static libraries, the _nonshared.a belong with their -dev packages and 206 libgcc.a, libgcov.a will be skipped in their packages 207 """ 208 209 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") and not '/usr/lib/debug-static/' in path and not '/.debug-static/' in path: 210 oe.qa.add_message(messages, "staticdev", "non -staticdev package contains static .a library: %s path '%s'" % \ 211 (name, package_qa_clean_path(path,d, name))) 212 213QAPATHTEST[mime] = "package_qa_check_mime" 214def package_qa_check_mime(path, name, d, elf, messages): 215 """ 216 Check if package installs mime types to /usr/share/mime/packages 217 while no inheriting mime.bbclass 218 """ 219 220 if d.getVar("datadir") + "/mime/packages" in path and path.endswith('.xml') and not bb.data.inherits_class("mime", d): 221 oe.qa.add_message(messages, "mime", "package contains mime types but does not inherit mime: %s path '%s'" % \ 222 (name, package_qa_clean_path(path,d))) 223 224QAPATHTEST[mime-xdg] = "package_qa_check_mime_xdg" 225def package_qa_check_mime_xdg(path, name, d, elf, messages): 226 """ 227 Check if package installs desktop file containing MimeType and requires 228 mime-types.bbclass to create /usr/share/applications/mimeinfo.cache 229 """ 230 231 if d.getVar("datadir") + "/applications" in path and path.endswith('.desktop') and not bb.data.inherits_class("mime-xdg", d): 232 mime_type_found = False 233 try: 234 with open(path, 'r') as f: 235 for line in f.read().split('\n'): 236 if 'MimeType' in line: 237 mime_type_found = True 238 break; 239 except: 240 # At least libreoffice installs symlinks with absolute paths that are dangling here. 241 # We could implement some magic but for few (one) recipes it is not worth the effort so just warn: 242 wstr = "%s cannot open %s - is it a symlink with absolute path?\n" % (name, package_qa_clean_path(path,d)) 243 wstr += "Please check if (linked) file contains key 'MimeType'.\n" 244 pkgname = name 245 if name == d.getVar('PN'): 246 pkgname = '${PN}' 247 wstr += "If yes: add \'inhert mime-xdg\' and \'MIME_XDG_PACKAGES += \"%s\"\' / if no add \'INSANE_SKIP:%s += \"mime-xdg\"\' to recipe." % (pkgname, pkgname) 248 oe.qa.add_message(messages, "mime-xdg", wstr) 249 if mime_type_found: 250 oe.qa.add_message(messages, "mime-xdg", "package contains desktop file with key 'MimeType' but does not inhert mime-xdg: %s path '%s'" % \ 251 (name, package_qa_clean_path(path,d))) 252 253def package_qa_check_libdir(d): 254 """ 255 Check for wrong library installation paths. For instance, catch 256 recipes installing /lib/bar.so when ${base_libdir}="lib32" or 257 installing in /usr/lib64 when ${libdir}="/usr/lib" 258 """ 259 import re 260 261 pkgdest = d.getVar('PKGDEST') 262 base_libdir = d.getVar("base_libdir") + os.sep 263 libdir = d.getVar("libdir") + os.sep 264 libexecdir = d.getVar("libexecdir") + os.sep 265 exec_prefix = d.getVar("exec_prefix") + os.sep 266 267 messages = [] 268 269 # The re's are purposely fuzzy, as some there are some .so.x.y.z files 270 # that don't follow the standard naming convention. It checks later 271 # that they are actual ELF files 272 lib_re = re.compile(r"^/lib.+\.so(\..+)?$") 273 exec_re = re.compile(r"^%s.*/lib.+\.so(\..+)?$" % exec_prefix) 274 275 for root, dirs, files in os.walk(pkgdest): 276 if root == pkgdest: 277 # Skip subdirectories for any packages with libdir in INSANE_SKIP 278 skippackages = [] 279 for package in dirs: 280 if 'libdir' in (d.getVar('INSANE_SKIP:' + package) or "").split(): 281 bb.note("Package %s skipping libdir QA test" % (package)) 282 skippackages.append(package) 283 elif d.getVar('PACKAGE_DEBUG_SPLIT_STYLE') == 'debug-file-directory' and package.endswith("-dbg"): 284 bb.note("Package %s skipping libdir QA test for PACKAGE_DEBUG_SPLIT_STYLE equals debug-file-directory" % (package)) 285 skippackages.append(package) 286 for package in skippackages: 287 dirs.remove(package) 288 for file in files: 289 full_path = os.path.join(root, file) 290 rel_path = os.path.relpath(full_path, pkgdest) 291 if os.sep in rel_path: 292 package, rel_path = rel_path.split(os.sep, 1) 293 rel_path = os.sep + rel_path 294 if lib_re.match(rel_path): 295 if base_libdir not in rel_path: 296 # make sure it's an actual ELF file 297 elf = oe.qa.ELFFile(full_path) 298 try: 299 elf.open() 300 messages.append("%s: found library in wrong location: %s" % (package, rel_path)) 301 except (oe.qa.NotELFFileError): 302 pass 303 if exec_re.match(rel_path): 304 if libdir not in rel_path and libexecdir not in rel_path: 305 # make sure it's an actual ELF file 306 elf = oe.qa.ELFFile(full_path) 307 try: 308 elf.open() 309 messages.append("%s: found library in wrong location: %s" % (package, rel_path)) 310 except (oe.qa.NotELFFileError): 311 pass 312 313 if messages: 314 oe.qa.handle_error("libdir", "\n".join(messages), d) 315 316QAPATHTEST[debug-files] = "package_qa_check_dbg" 317def package_qa_check_dbg(path, name, d, elf, messages): 318 """ 319 Check for ".debug" files or directories outside of the dbg package 320 """ 321 322 if not "-dbg" in name and not "-ptest" in name: 323 if '.debug' in path.split(os.path.sep): 324 oe.qa.add_message(messages, "debug-files", "non debug package contains .debug directory: %s path %s" % \ 325 (name, package_qa_clean_path(path,d))) 326 327QAPATHTEST[arch] = "package_qa_check_arch" 328def package_qa_check_arch(path,name,d, elf, messages): 329 """ 330 Check if archs are compatible 331 """ 332 import re, oe.elf 333 334 if not elf: 335 return 336 337 target_os = d.getVar('HOST_OS') 338 target_arch = d.getVar('HOST_ARCH') 339 provides = d.getVar('PROVIDES') 340 bpn = d.getVar('BPN') 341 342 if target_arch == "allarch": 343 pn = d.getVar('PN') 344 oe.qa.add_message(messages, "arch", pn + ": Recipe inherits the allarch class, but has packaged architecture-specific binaries") 345 return 346 347 # FIXME: Cross package confuse this check, so just skip them 348 for s in ['cross', 'nativesdk', 'cross-canadian']: 349 if bb.data.inherits_class(s, d): 350 return 351 352 # avoid following links to /usr/bin (e.g. on udev builds) 353 # we will check the files pointed to anyway... 354 if os.path.islink(path): 355 return 356 357 #if this will throw an exception, then fix the dict above 358 (machine, osabi, abiversion, littleendian, bits) \ 359 = oe.elf.machine_dict(d)[target_os][target_arch] 360 361 # Check the architecture and endiannes of the binary 362 is_32 = (("virtual/kernel" in provides) or bb.data.inherits_class("module", d)) and \ 363 (target_os == "linux-gnux32" or target_os == "linux-muslx32" or \ 364 target_os == "linux-gnu_ilp32" or re.match(r'mips64.*32', d.getVar('DEFAULTTUNE'))) 365 is_bpf = (oe.qa.elf_machine_to_string(elf.machine()) == "BPF") 366 if not ((machine == elf.machine()) or is_32 or is_bpf): 367 oe.qa.add_message(messages, "arch", "Architecture did not match (%s, expected %s) in %s" % \ 368 (oe.qa.elf_machine_to_string(elf.machine()), oe.qa.elf_machine_to_string(machine), package_qa_clean_path(path, d, name))) 369 elif not ((bits == elf.abiSize()) or is_32 or is_bpf): 370 oe.qa.add_message(messages, "arch", "Bit size did not match (%d, expected %d) in %s" % \ 371 (elf.abiSize(), bits, package_qa_clean_path(path, d, name))) 372 elif not ((littleendian == elf.isLittleEndian()) or is_bpf): 373 oe.qa.add_message(messages, "arch", "Endiannes did not match (%d, expected %d) in %s" % \ 374 (elf.isLittleEndian(), littleendian, package_qa_clean_path(path,d, name))) 375 376QAPATHTEST[desktop] = "package_qa_check_desktop" 377def package_qa_check_desktop(path, name, d, elf, messages): 378 """ 379 Run all desktop files through desktop-file-validate. 380 """ 381 if path.endswith(".desktop"): 382 desktop_file_validate = os.path.join(d.getVar('STAGING_BINDIR_NATIVE'),'desktop-file-validate') 383 output = os.popen("%s %s" % (desktop_file_validate, path)) 384 # This only produces output on errors 385 for l in output: 386 oe.qa.add_message(messages, "desktop", "Desktop file issue: " + l.strip()) 387 388QAPATHTEST[textrel] = "package_qa_textrel" 389def package_qa_textrel(path, name, d, elf, messages): 390 """ 391 Check if the binary contains relocations in .text 392 """ 393 394 if not elf: 395 return 396 397 if os.path.islink(path): 398 return 399 400 phdrs = elf.run_objdump("-p", d) 401 sane = True 402 403 import re 404 textrel_re = re.compile(r"\s+TEXTREL\s+") 405 for line in phdrs.split("\n"): 406 if textrel_re.match(line): 407 sane = False 408 break 409 410 if not sane: 411 path = package_qa_clean_path(path, d, name) 412 oe.qa.add_message(messages, "textrel", "%s: ELF binary %s has relocations in .text" % (name, path)) 413 414QAPATHTEST[ldflags] = "package_qa_hash_style" 415def package_qa_hash_style(path, name, d, elf, messages): 416 """ 417 Check if the binary has the right hash style... 418 """ 419 420 if not elf: 421 return 422 423 if os.path.islink(path): 424 return 425 426 gnu_hash = "--hash-style=gnu" in d.getVar('LDFLAGS') 427 if not gnu_hash: 428 gnu_hash = "--hash-style=both" in d.getVar('LDFLAGS') 429 if not gnu_hash: 430 return 431 432 sane = False 433 has_syms = False 434 435 phdrs = elf.run_objdump("-p", d) 436 437 # If this binary has symbols, we expect it to have GNU_HASH too. 438 for line in phdrs.split("\n"): 439 if "SYMTAB" in line: 440 has_syms = True 441 if "GNU_HASH" in line or "MIPS_XHASH" in line: 442 sane = True 443 if ("[mips32]" in line or "[mips64]" in line) and d.getVar('TCLIBC') == "musl": 444 sane = True 445 if has_syms and not sane: 446 path = package_qa_clean_path(path, d, name) 447 oe.qa.add_message(messages, "ldflags", "File %s in package %s doesn't have GNU_HASH (didn't pass LDFLAGS?)" % (path, name)) 448 449 450QAPATHTEST[buildpaths] = "package_qa_check_buildpaths" 451def package_qa_check_buildpaths(path, name, d, elf, messages): 452 """ 453 Check for build paths inside target files and error if paths are not 454 explicitly ignored. 455 """ 456 import stat 457 458 # Ignore symlinks/devs/fifos 459 mode = os.lstat(path).st_mode 460 if stat.S_ISLNK(mode) or stat.S_ISBLK(mode) or stat.S_ISFIFO(mode) or stat.S_ISCHR(mode) or stat.S_ISSOCK(mode): 461 return 462 463 tmpdir = bytes(d.getVar('TMPDIR'), encoding="utf-8") 464 with open(path, 'rb') as f: 465 file_content = f.read() 466 if tmpdir in file_content: 467 trimmed = path.replace(os.path.join (d.getVar("PKGDEST"), name), "") 468 oe.qa.add_message(messages, "buildpaths", "File %s in package %s contains reference to TMPDIR" % (trimmed, name)) 469 470 471QAPATHTEST[xorg-driver-abi] = "package_qa_check_xorg_driver_abi" 472def package_qa_check_xorg_driver_abi(path, name, d, elf, messages): 473 """ 474 Check that all packages containing Xorg drivers have ABI dependencies 475 """ 476 477 # Skip dev, dbg or nativesdk packages 478 if name.endswith("-dev") or name.endswith("-dbg") or name.startswith("nativesdk-"): 479 return 480 481 driverdir = d.expand("${libdir}/xorg/modules/drivers/") 482 if driverdir in path and path.endswith(".so"): 483 mlprefix = d.getVar('MLPREFIX') or '' 484 for rdep in bb.utils.explode_deps(d.getVar('RDEPENDS:' + name) or ""): 485 if rdep.startswith("%sxorg-abi-" % mlprefix): 486 return 487 oe.qa.add_message(messages, "xorg-driver-abi", "Package %s contains Xorg driver (%s) but no xorg-abi- dependencies" % (name, os.path.basename(path))) 488 489QAPATHTEST[infodir] = "package_qa_check_infodir" 490def package_qa_check_infodir(path, name, d, elf, messages): 491 """ 492 Check that /usr/share/info/dir isn't shipped in a particular package 493 """ 494 infodir = d.expand("${infodir}/dir") 495 496 if infodir in path: 497 oe.qa.add_message(messages, "infodir", "The /usr/share/info/dir file is not meant to be shipped in a particular package.") 498 499QAPATHTEST[symlink-to-sysroot] = "package_qa_check_symlink_to_sysroot" 500def package_qa_check_symlink_to_sysroot(path, name, d, elf, messages): 501 """ 502 Check that the package doesn't contain any absolute symlinks to the sysroot. 503 """ 504 if os.path.islink(path): 505 target = os.readlink(path) 506 if os.path.isabs(target): 507 tmpdir = d.getVar('TMPDIR') 508 if target.startswith(tmpdir): 509 trimmed = path.replace(os.path.join (d.getVar("PKGDEST"), name), "") 510 oe.qa.add_message(messages, "symlink-to-sysroot", "Symlink %s in %s points to TMPDIR" % (trimmed, name)) 511 512QAPATHTEST[32bit-time] = "check_32bit_symbols" 513def check_32bit_symbols(path, packagename, d, elf, messages): 514 """ 515 Check that ELF files do not use any 32 bit time APIs from glibc. 516 """ 517 thirtytwo_bit_time_archs = set(('arm','armeb','mipsarcho32','powerpc','x86')) 518 overrides = set(d.getVar('OVERRIDES').split(':')) 519 if not(thirtytwo_bit_time_archs & overrides): 520 return 521 522 import re 523 # This list is manually constructed by searching the image folder of the 524 # glibc recipe for __USE_TIME_BITS64. There is no good way to do this 525 # automatically. 526 api32 = { 527 # /usr/include/time.h 528 "clock_getres", "clock_gettime", "clock_nanosleep", "clock_settime", 529 "ctime", "ctime_r", "difftime", "gmtime", "gmtime_r", "localtime", 530 "localtime_r", "mktime", "nanosleep", "time", "timegm", "timelocal", 531 "timer_gettime", "timer_settime", "timespec_get", "timespec_getres", 532 # /usr/include/bits/time.h 533 "clock_adjtime", 534 # /usr/include/signal.h 535 "sigtimedwait", 536 # /usr/include/sys/time.h 537 "futimes", "futimesat", "getitimer", "gettimeofday", "lutimes", 538 "setitimer", "settimeofday", "utimes", 539 # /usr/include/sys/timex.h 540 "adjtimex", "ntp_adjtime", "ntp_gettime", "ntp_gettimex", 541 # /usr/include/sys/wait.h 542 "wait3", "wait4", 543 # /usr/include/sys/stat.h 544 "fstat", "fstat64", "fstatat", "fstatat64", "futimens", "lstat", 545 "lstat64", "stat", "stat64", "utimensat", 546 # /usr/include/sys/poll.h 547 "ppoll", 548 # /usr/include/sys/resource.h 549 "getrusage", 550 # /usr/include/sys/ioctl.h 551 "ioctl", 552 # /usr/include/sys/select.h 553 "select", "pselect", 554 # /usr/include/sys/prctl.h 555 "prctl", 556 # /usr/include/sys/epoll.h 557 "epoll_pwait2", 558 # /usr/include/sys/timerfd.h 559 "timerfd_gettime", "timerfd_settime", 560 # /usr/include/sys/socket.h 561 "getsockopt", "recvmmsg", "recvmsg", "sendmmsg", "sendmsg", 562 "setsockopt", 563 # /usr/include/sys/msg.h 564 "msgctl", 565 # /usr/include/sys/sem.h 566 "semctl", "semtimedop", 567 # /usr/include/sys/shm.h 568 "shmctl", 569 # /usr/include/pthread.h 570 "pthread_clockjoin_np", "pthread_cond_clockwait", 571 "pthread_cond_timedwait", "pthread_mutex_clocklock", 572 "pthread_mutex_timedlock", "pthread_rwlock_clockrdlock", 573 "pthread_rwlock_clockwrlock", "pthread_rwlock_timedrdlock", 574 "pthread_rwlock_timedwrlock", "pthread_timedjoin_np", 575 # /usr/include/semaphore.h 576 "sem_clockwait", "sem_timedwait", 577 # /usr/include/threads.h 578 "cnd_timedwait", "mtx_timedlock", "thrd_sleep", 579 # /usr/include/aio.h 580 "aio_cancel", "aio_error", "aio_read", "aio_return", "aio_suspend", 581 "aio_write", "lio_listio", 582 # /usr/include/mqueue.h 583 "mq_timedreceive", "mq_timedsend", 584 # /usr/include/glob.h 585 "glob", "glob64", "globfree", "globfree64", 586 # /usr/include/sched.h 587 "sched_rr_get_interval", 588 # /usr/include/fcntl.h 589 "fcntl", "fcntl64", 590 # /usr/include/utime.h 591 "utime", 592 # /usr/include/ftw.h 593 "ftw", "ftw64", "nftw", "nftw64", 594 # /usr/include/fts.h 595 "fts64_children", "fts64_close", "fts64_open", "fts64_read", 596 "fts64_set", "fts_children", "fts_close", "fts_open", "fts_read", 597 "fts_set", 598 # /usr/include/netdb.h 599 "gai_suspend", 600 } 601 602 ptrn = re.compile( 603 r''' 604 (?P<value>[\da-fA-F]+) \s+ 605 (?P<flags>[lgu! ][w ][C ][W ][Ii ][dD ]F) \s+ 606 (?P<section>\*UND\*) \s+ 607 (?P<alignment>(?P<size>[\da-fA-F]+)) \s+ 608 (?P<symbol> 609 ''' + 610 r'(?P<notag>' + r'|'.join(sorted(api32)) + r')' + 611 r''' 612 (@+(?P<tag>GLIBC_\d+\.\d+\S*))) 613 ''', re.VERBOSE 614 ) 615 616 # elf is a oe.qa.ELFFile object 617 if elf is not None: 618 phdrs = elf.run_objdump("-tw", d) 619 syms = re.finditer(ptrn, phdrs) 620 usedapis = {sym.group('notag') for sym in syms} 621 if usedapis: 622 elfpath = package_qa_clean_path(path, d, packagename) 623 # Remove any .debug dir, heuristic that probably works 624 # At this point, any symbol information is stripped into the debug 625 # package, so that is the only place we will find them. 626 elfpath = elfpath.replace('.debug/', '') 627 allowed = "32bit-time" in (d.getVar('INSANE_SKIP') or '').split() 628 if not allowed: 629 msgformat = elfpath + " uses 32-bit api '%s'" 630 for sym in usedapis: 631 oe.qa.add_message(messages, '32bit-time', msgformat % sym) 632 oe.qa.add_message( 633 messages, '32bit-time', 634 'Suppress with INSANE_SKIP = "32bit-time"' 635 ) 636 637# Check license variables 638do_populate_lic[postfuncs] += "populate_lic_qa_checksum" 639python populate_lic_qa_checksum() { 640 """ 641 Check for changes in the license files. 642 """ 643 644 lic_files = d.getVar('LIC_FILES_CHKSUM') or '' 645 lic = d.getVar('LICENSE') 646 pn = d.getVar('PN') 647 648 if lic == "CLOSED": 649 return 650 651 if not lic_files and d.getVar('SRC_URI'): 652 oe.qa.handle_error("license-checksum", pn + ": Recipe file fetches files and does not have license file information (LIC_FILES_CHKSUM)", d) 653 654 srcdir = d.getVar('S') 655 corebase_licensefile = d.getVar('COREBASE') + "/LICENSE" 656 for url in lic_files.split(): 657 try: 658 (type, host, path, user, pswd, parm) = bb.fetch.decodeurl(url) 659 except bb.fetch.MalformedUrl: 660 oe.qa.handle_error("license-checksum", pn + ": LIC_FILES_CHKSUM contains an invalid URL: " + url, d) 661 continue 662 srclicfile = os.path.join(srcdir, path) 663 if not os.path.isfile(srclicfile): 664 oe.qa.handle_error("license-checksum", pn + ": LIC_FILES_CHKSUM points to an invalid file: " + srclicfile, d) 665 continue 666 667 if (srclicfile == corebase_licensefile): 668 bb.warn("${COREBASE}/LICENSE is not a valid license file, please use '${COMMON_LICENSE_DIR}/MIT' for a MIT License file in LIC_FILES_CHKSUM. This will become an error in the future") 669 670 recipemd5 = parm.get('md5', '') 671 beginline, endline = 0, 0 672 if 'beginline' in parm: 673 beginline = int(parm['beginline']) 674 if 'endline' in parm: 675 endline = int(parm['endline']) 676 677 if (not beginline) and (not endline): 678 md5chksum = bb.utils.md5_file(srclicfile) 679 with open(srclicfile, 'r', errors='replace') as f: 680 license = f.read().splitlines() 681 else: 682 with open(srclicfile, 'rb') as f: 683 import hashlib 684 lineno = 0 685 license = [] 686 try: 687 m = hashlib.new('MD5', usedforsecurity=False) 688 except TypeError: 689 m = hashlib.new('MD5') 690 for line in f: 691 lineno += 1 692 if (lineno >= beginline): 693 if ((lineno <= endline) or not endline): 694 m.update(line) 695 license.append(line.decode('utf-8', errors='replace').rstrip()) 696 else: 697 break 698 md5chksum = m.hexdigest() 699 if recipemd5 == md5chksum: 700 bb.note (pn + ": md5 checksum matched for ", url) 701 else: 702 if recipemd5: 703 msg = pn + ": The LIC_FILES_CHKSUM does not match for " + url 704 msg = msg + "\n" + pn + ": The new md5 checksum is " + md5chksum 705 max_lines = int(d.getVar('QA_MAX_LICENSE_LINES') or 20) 706 if not license or license[-1] != '': 707 # Ensure that our license text ends with a line break 708 # (will be added with join() below). 709 license.append('') 710 remove = len(license) - max_lines 711 if remove > 0: 712 start = max_lines // 2 713 end = start + remove - 1 714 del license[start:end] 715 license.insert(start, '...') 716 msg = msg + "\n" + pn + ": Here is the selected license text:" + \ 717 "\n" + \ 718 "{:v^70}".format(" beginline=%d " % beginline if beginline else "") + \ 719 "\n" + "\n".join(license) + \ 720 "{:^^70}".format(" endline=%d " % endline if endline else "") 721 if beginline: 722 if endline: 723 srcfiledesc = "%s (lines %d through to %d)" % (srclicfile, beginline, endline) 724 else: 725 srcfiledesc = "%s (beginning on line %d)" % (srclicfile, beginline) 726 elif endline: 727 srcfiledesc = "%s (ending on line %d)" % (srclicfile, endline) 728 else: 729 srcfiledesc = srclicfile 730 msg = msg + "\n" + pn + ": Check if the license information has changed in %s to verify that the LICENSE value \"%s\" remains valid" % (srcfiledesc, lic) 731 732 else: 733 msg = pn + ": LIC_FILES_CHKSUM is not specified for " + url 734 msg = msg + "\n" + pn + ": The md5 checksum is " + md5chksum 735 oe.qa.handle_error("license-checksum", msg, d) 736 737 oe.qa.exit_if_errors(d) 738} 739 740def qa_check_staged(path,d): 741 """ 742 Check staged la and pc files for common problems like references to the work 743 directory. 744 745 As this is run after every stage we should be able to find the one 746 responsible for the errors easily even if we look at every .pc and .la file. 747 """ 748 749 tmpdir = d.getVar('TMPDIR') 750 workdir = os.path.join(tmpdir, "work") 751 recipesysroot = d.getVar("RECIPE_SYSROOT") 752 753 if bb.data.inherits_class("native", d) or bb.data.inherits_class("cross", d): 754 pkgconfigcheck = workdir 755 else: 756 pkgconfigcheck = tmpdir 757 758 skip = (d.getVar('INSANE_SKIP') or "").split() 759 skip_la = False 760 if 'la' in skip: 761 bb.note("Recipe %s skipping qa checking: la" % d.getVar('PN')) 762 skip_la = True 763 764 skip_pkgconfig = False 765 if 'pkgconfig' in skip: 766 bb.note("Recipe %s skipping qa checking: pkgconfig" % d.getVar('PN')) 767 skip_pkgconfig = True 768 769 skip_shebang_size = False 770 if 'shebang-size' in skip: 771 bb.note("Recipe %s skipping qa checkking: shebang-size" % d.getVar('PN')) 772 skip_shebang_size = True 773 774 # find all .la and .pc files 775 # read the content 776 # and check for stuff that looks wrong 777 for root, dirs, files in os.walk(path): 778 for file in files: 779 path = os.path.join(root,file) 780 if file.endswith(".la") and not skip_la: 781 with open(path) as f: 782 file_content = f.read() 783 file_content = file_content.replace(recipesysroot, "") 784 if workdir in file_content: 785 error_msg = "%s failed sanity test (workdir) in path %s" % (file,root) 786 oe.qa.handle_error("la", error_msg, d) 787 elif file.endswith(".pc") and not skip_pkgconfig: 788 with open(path) as f: 789 file_content = f.read() 790 file_content = file_content.replace(recipesysroot, "") 791 if pkgconfigcheck in file_content: 792 error_msg = "%s failed sanity test (tmpdir) in path %s" % (file,root) 793 oe.qa.handle_error("pkgconfig", error_msg, d) 794 795 if not skip_shebang_size: 796 errors = {} 797 package_qa_check_shebang_size(path, "", d, None, errors) 798 for e in errors: 799 oe.qa.handle_error(e, errors[e], d) 800 801 802# Run all package-wide warnfuncs and errorfuncs 803def package_qa_package(warnfuncs, errorfuncs, package, d): 804 warnings = {} 805 errors = {} 806 807 for func in warnfuncs: 808 func(package, d, warnings) 809 for func in errorfuncs: 810 func(package, d, errors) 811 812 for w in warnings: 813 oe.qa.handle_error(w, warnings[w], d) 814 for e in errors: 815 oe.qa.handle_error(e, errors[e], d) 816 817 return len(errors) == 0 818 819# Run all recipe-wide warnfuncs and errorfuncs 820def package_qa_recipe(warnfuncs, errorfuncs, pn, d): 821 warnings = {} 822 errors = {} 823 824 for func in warnfuncs: 825 func(pn, d, warnings) 826 for func in errorfuncs: 827 func(pn, d, errors) 828 829 for w in warnings: 830 oe.qa.handle_error(w, warnings[w], d) 831 for e in errors: 832 oe.qa.handle_error(e, errors[e], d) 833 834 return len(errors) == 0 835 836def prepopulate_objdump_p(elf, d): 837 output = elf.run_objdump("-p", d) 838 return (elf.name, output) 839 840# Walk over all files in a directory and call func 841def package_qa_walk(warnfuncs, errorfuncs, package, d): 842 #if this will throw an exception, then fix the dict above 843 target_os = d.getVar('HOST_OS') 844 target_arch = d.getVar('HOST_ARCH') 845 846 warnings = {} 847 errors = {} 848 elves = {} 849 for path in pkgfiles[package]: 850 elf = None 851 if os.path.isfile(path): 852 elf = oe.qa.ELFFile(path) 853 try: 854 elf.open() 855 elf.close() 856 except oe.qa.NotELFFileError: 857 elf = None 858 if elf: 859 elves[path] = elf 860 861 results = oe.utils.multiprocess_launch(prepopulate_objdump_p, elves.values(), d, extraargs=(d,)) 862 for item in results: 863 elves[item[0]].set_objdump("-p", item[1]) 864 865 for path in pkgfiles[package]: 866 if path in elves: 867 elves[path].open() 868 for func in warnfuncs: 869 func(path, package, d, elves.get(path), warnings) 870 for func in errorfuncs: 871 func(path, package, d, elves.get(path), errors) 872 if path in elves: 873 elves[path].close() 874 875 for w in warnings: 876 oe.qa.handle_error(w, warnings[w], d) 877 for e in errors: 878 oe.qa.handle_error(e, errors[e], d) 879 880def package_qa_check_rdepends(pkg, pkgdest, skip, taskdeps, packages, d): 881 # Don't do this check for kernel/module recipes, there aren't too many debug/development 882 # packages and you can get false positives e.g. on kernel-module-lirc-dev 883 if bb.data.inherits_class("kernel", d) or bb.data.inherits_class("module-base", d): 884 return 885 886 if not "-dbg" in pkg and not "packagegroup-" in pkg and not "-image" in pkg: 887 localdata = bb.data.createCopy(d) 888 localdata.setVar('OVERRIDES', localdata.getVar('OVERRIDES') + ':' + pkg) 889 890 # Now check the RDEPENDS 891 rdepends = bb.utils.explode_deps(localdata.getVar('RDEPENDS') or "") 892 893 # Now do the sanity check!!! 894 if "build-deps" not in skip: 895 for rdepend in rdepends: 896 if "-dbg" in rdepend and "debug-deps" not in skip: 897 error_msg = "%s rdepends on %s" % (pkg,rdepend) 898 oe.qa.handle_error("debug-deps", error_msg, d) 899 if (not "-dev" in pkg and not "-staticdev" in pkg) and rdepend.endswith("-dev") and "dev-deps" not in skip: 900 error_msg = "%s rdepends on %s" % (pkg, rdepend) 901 oe.qa.handle_error("dev-deps", error_msg, d) 902 if rdepend not in packages: 903 rdep_data = oe.packagedata.read_subpkgdata(rdepend, d) 904 if rdep_data and 'PN' in rdep_data and rdep_data['PN'] in taskdeps: 905 continue 906 if not rdep_data or not 'PN' in rdep_data: 907 pkgdata_dir = d.getVar("PKGDATA_DIR") 908 try: 909 possibles = os.listdir("%s/runtime-rprovides/%s/" % (pkgdata_dir, rdepend)) 910 except OSError: 911 possibles = [] 912 for p in possibles: 913 rdep_data = oe.packagedata.read_subpkgdata(p, d) 914 if rdep_data and 'PN' in rdep_data and rdep_data['PN'] in taskdeps: 915 break 916 if rdep_data and 'PN' in rdep_data and rdep_data['PN'] in taskdeps: 917 continue 918 if rdep_data and 'PN' in rdep_data: 919 error_msg = "%s rdepends on %s, but it isn't a build dependency, missing %s in DEPENDS or PACKAGECONFIG?" % (pkg, rdepend, rdep_data['PN']) 920 else: 921 error_msg = "%s rdepends on %s, but it isn't a build dependency?" % (pkg, rdepend) 922 oe.qa.handle_error("build-deps", error_msg, d) 923 924 if "file-rdeps" not in skip: 925 ignored_file_rdeps = set(['/bin/sh', '/usr/bin/env', 'rtld(GNU_HASH)']) 926 if bb.data.inherits_class('nativesdk', d): 927 ignored_file_rdeps |= set(['/bin/bash', '/usr/bin/perl', 'perl']) 928 # For Saving the FILERDEPENDS 929 filerdepends = {} 930 rdep_data = oe.packagedata.read_subpkgdata(pkg, d) 931 for key in rdep_data: 932 if key.startswith("FILERDEPENDS:"): 933 for subkey in bb.utils.explode_deps(rdep_data[key]): 934 if subkey not in ignored_file_rdeps and \ 935 not subkey.startswith('perl('): 936 # We already know it starts with FILERDEPENDS_ 937 filerdepends[subkey] = key[13:] 938 939 if filerdepends: 940 done = rdepends[:] 941 # Add the rprovides of itself 942 if pkg not in done: 943 done.insert(0, pkg) 944 945 # The python is not a package, but python-core provides it, so 946 # skip checking /usr/bin/python if python is in the rdeps, in 947 # case there is a RDEPENDS:pkg = "python" in the recipe. 948 for py in [ d.getVar('MLPREFIX') + "python", "python" ]: 949 if py in done: 950 filerdepends.pop("/usr/bin/python",None) 951 done.remove(py) 952 for rdep in done: 953 # The file dependencies may contain package names, e.g., 954 # perl 955 filerdepends.pop(rdep,None) 956 957 # For Saving the FILERPROVIDES, RPROVIDES and FILES_INFO 958 rdep_data = oe.packagedata.read_subpkgdata(rdep, d) 959 for key in rdep_data: 960 if key.startswith("FILERPROVIDES:") or key.startswith("RPROVIDES:"): 961 for subkey in bb.utils.explode_deps(rdep_data[key]): 962 filerdepends.pop(subkey,None) 963 # Add the files list to the rprovides 964 if key.startswith("FILES_INFO:"): 965 # Use eval() to make it as a dict 966 for subkey in eval(rdep_data[key]): 967 filerdepends.pop(subkey,None) 968 if not filerdepends: 969 # Break if all the file rdepends are met 970 break 971 if filerdepends: 972 for key in filerdepends: 973 error_msg = "%s contained in package %s requires %s, but no providers found in RDEPENDS:%s?" % \ 974 (filerdepends[key].replace(":%s" % pkg, "").replace("@underscore@", "_"), pkg, key, pkg) 975 oe.qa.handle_error("file-rdeps", error_msg, d) 976package_qa_check_rdepends[vardepsexclude] = "OVERRIDES" 977 978def package_qa_check_deps(pkg, pkgdest, d): 979 980 localdata = bb.data.createCopy(d) 981 localdata.setVar('OVERRIDES', pkg) 982 983 def check_valid_deps(var): 984 try: 985 rvar = bb.utils.explode_dep_versions2(localdata.getVar(var) or "") 986 except ValueError as e: 987 bb.fatal("%s:%s: %s" % (var, pkg, e)) 988 for dep in rvar: 989 for v in rvar[dep]: 990 if v and not v.startswith(('< ', '= ', '> ', '<= ', '>=')): 991 error_msg = "%s:%s is invalid: %s (%s) only comparisons <, =, >, <=, and >= are allowed" % (var, pkg, dep, v) 992 oe.qa.handle_error("dep-cmp", error_msg, d) 993 994 check_valid_deps('RDEPENDS') 995 check_valid_deps('RRECOMMENDS') 996 check_valid_deps('RSUGGESTS') 997 check_valid_deps('RPROVIDES') 998 check_valid_deps('RREPLACES') 999 check_valid_deps('RCONFLICTS') 1000 1001QAPKGTEST[usrmerge] = "package_qa_check_usrmerge" 1002def package_qa_check_usrmerge(pkg, d, messages): 1003 1004 pkgdest = d.getVar('PKGDEST') 1005 pkg_dir = pkgdest + os.sep + pkg + os.sep 1006 merged_dirs = ['bin', 'sbin', 'lib'] + d.getVar('MULTILIB_VARIANTS').split() 1007 for f in merged_dirs: 1008 if os.path.exists(pkg_dir + f) and not os.path.islink(pkg_dir + f): 1009 msg = "%s package is not obeying usrmerge distro feature. /%s should be relocated to /usr." % (pkg, f) 1010 oe.qa.add_message(messages, "usrmerge", msg) 1011 return False 1012 return True 1013 1014QAPKGTEST[perllocalpod] = "package_qa_check_perllocalpod" 1015def package_qa_check_perllocalpod(pkg, d, messages): 1016 """ 1017 Check that the recipe didn't ship a perlocal.pod file, which shouldn't be 1018 installed in a distribution package. cpan.bbclass sets NO_PERLLOCAL=1 to 1019 handle this for most recipes. 1020 """ 1021 import glob 1022 pkgd = oe.path.join(d.getVar('PKGDEST'), pkg) 1023 podpath = oe.path.join(pkgd, d.getVar("libdir"), "perl*", "*", "*", "perllocal.pod") 1024 1025 matches = glob.glob(podpath) 1026 if matches: 1027 matches = [package_qa_clean_path(path, d, pkg) for path in matches] 1028 msg = "%s contains perllocal.pod (%s), should not be installed" % (pkg, " ".join(matches)) 1029 oe.qa.add_message(messages, "perllocalpod", msg) 1030 1031QAPKGTEST[expanded-d] = "package_qa_check_expanded_d" 1032def package_qa_check_expanded_d(package, d, messages): 1033 """ 1034 Check for the expanded D (${D}) value in pkg_* and FILES 1035 variables, warn the user to use it correctly. 1036 """ 1037 sane = True 1038 expanded_d = d.getVar('D') 1039 1040 for var in 'FILES','pkg_preinst', 'pkg_postinst', 'pkg_prerm', 'pkg_postrm': 1041 bbvar = d.getVar(var + ":" + package) or "" 1042 if expanded_d in bbvar: 1043 if var == 'FILES': 1044 oe.qa.add_message(messages, "expanded-d", "FILES in %s recipe should not contain the ${D} variable as it references the local build directory not the target filesystem, best solution is to remove the ${D} reference" % package) 1045 sane = False 1046 else: 1047 oe.qa.add_message(messages, "expanded-d", "%s in %s recipe contains ${D}, it should be replaced by $D instead" % (var, package)) 1048 sane = False 1049 return sane 1050 1051QAPKGTEST[unlisted-pkg-lics] = "package_qa_check_unlisted_pkg_lics" 1052def package_qa_check_unlisted_pkg_lics(package, d, messages): 1053 """ 1054 Check that all licenses for a package are among the licenses for the recipe. 1055 """ 1056 pkg_lics = d.getVar('LICENSE:' + package) 1057 if not pkg_lics: 1058 return True 1059 1060 recipe_lics_set = oe.license.list_licenses(d.getVar('LICENSE')) 1061 package_lics = oe.license.list_licenses(pkg_lics) 1062 unlisted = package_lics - recipe_lics_set 1063 if unlisted: 1064 oe.qa.add_message(messages, "unlisted-pkg-lics", 1065 "LICENSE:%s includes licenses (%s) that are not " 1066 "listed in LICENSE" % (package, ' '.join(unlisted))) 1067 return False 1068 obsolete = set(oe.license.obsolete_license_list()) & package_lics - recipe_lics_set 1069 if obsolete: 1070 oe.qa.add_message(messages, "obsolete-license", 1071 "LICENSE:%s includes obsolete licenses %s" % (package, ' '.join(obsolete))) 1072 return False 1073 return True 1074 1075QAPKGTEST[empty-dirs] = "package_qa_check_empty_dirs" 1076def package_qa_check_empty_dirs(pkg, d, messages): 1077 """ 1078 Check for the existence of files in directories that are expected to be 1079 empty. 1080 """ 1081 1082 pkgd = oe.path.join(d.getVar('PKGDEST'), pkg) 1083 for dir in (d.getVar('QA_EMPTY_DIRS') or "").split(): 1084 empty_dir = oe.path.join(pkgd, dir) 1085 if os.path.exists(empty_dir) and os.listdir(empty_dir): 1086 recommendation = (d.getVar('QA_EMPTY_DIRS_RECOMMENDATION:' + dir) or 1087 "but it is expected to be empty") 1088 msg = "%s installs files in %s, %s" % (pkg, dir, recommendation) 1089 oe.qa.add_message(messages, "empty-dirs", msg) 1090 1091def package_qa_check_encoding(keys, encode, d): 1092 def check_encoding(key, enc): 1093 sane = True 1094 value = d.getVar(key) 1095 if value: 1096 try: 1097 s = value.encode(enc) 1098 except UnicodeDecodeError as e: 1099 error_msg = "%s has non %s characters" % (key,enc) 1100 sane = False 1101 oe.qa.handle_error("invalid-chars", error_msg, d) 1102 return sane 1103 1104 for key in keys: 1105 sane = check_encoding(key, encode) 1106 if not sane: 1107 break 1108 1109HOST_USER_UID := "${@os.getuid()}" 1110HOST_USER_GID := "${@os.getgid()}" 1111 1112QAPATHTEST[host-user-contaminated] = "package_qa_check_host_user" 1113def package_qa_check_host_user(path, name, d, elf, messages): 1114 """Check for paths outside of /home which are owned by the user running bitbake.""" 1115 1116 if not os.path.lexists(path): 1117 return 1118 1119 dest = d.getVar('PKGDEST') 1120 pn = d.getVar('PN') 1121 home = os.path.join(dest, name, 'home') 1122 if path == home or path.startswith(home + os.sep): 1123 return 1124 1125 try: 1126 stat = os.lstat(path) 1127 except OSError as exc: 1128 import errno 1129 if exc.errno != errno.ENOENT: 1130 raise 1131 else: 1132 check_uid = int(d.getVar('HOST_USER_UID')) 1133 if stat.st_uid == check_uid: 1134 oe.qa.add_message(messages, "host-user-contaminated", "%s: %s is owned by uid %d, which is the same as the user running bitbake. This may be due to host contamination" % (pn, package_qa_clean_path(path, d, name), check_uid)) 1135 return False 1136 1137 check_gid = int(d.getVar('HOST_USER_GID')) 1138 if stat.st_gid == check_gid: 1139 oe.qa.add_message(messages, "host-user-contaminated", "%s: %s is owned by gid %d, which is the same as the user running bitbake. This may be due to host contamination" % (pn, package_qa_clean_path(path, d, name), check_gid)) 1140 return False 1141 return True 1142 1143QARECIPETEST[unhandled-features-check] = "package_qa_check_unhandled_features_check" 1144def package_qa_check_unhandled_features_check(pn, d, messages): 1145 if not bb.data.inherits_class('features_check', d): 1146 var_set = False 1147 for kind in ['DISTRO', 'MACHINE', 'COMBINED']: 1148 for var in ['ANY_OF_' + kind + '_FEATURES', 'REQUIRED_' + kind + '_FEATURES', 'CONFLICT_' + kind + '_FEATURES']: 1149 if d.getVar(var) is not None or d.hasOverrides(var): 1150 var_set = True 1151 if var_set: 1152 oe.qa.handle_error("unhandled-features-check", "%s: recipe doesn't inherit features_check" % pn, d) 1153 1154QARECIPETEST[missing-update-alternatives] = "package_qa_check_missing_update_alternatives" 1155def package_qa_check_missing_update_alternatives(pn, d, messages): 1156 # Look at all packages and find out if any of those sets ALTERNATIVE variable 1157 # without inheriting update-alternatives class 1158 for pkg in (d.getVar('PACKAGES') or '').split(): 1159 if d.getVar('ALTERNATIVE:%s' % pkg) and not bb.data.inherits_class('update-alternatives', d): 1160 oe.qa.handle_error("missing-update-alternatives", "%s: recipe defines ALTERNATIVE:%s but doesn't inherit update-alternatives. This might fail during do_rootfs later!" % (pn, pkg), d) 1161 1162# The PACKAGE FUNC to scan each package 1163python do_package_qa () { 1164 import subprocess 1165 import oe.packagedata 1166 1167 bb.note("DO PACKAGE QA") 1168 1169 main_lic = d.getVar('LICENSE') 1170 1171 # Check for obsolete license references in main LICENSE (packages are checked below for any changes) 1172 main_licenses = oe.license.list_licenses(d.getVar('LICENSE')) 1173 obsolete = set(oe.license.obsolete_license_list()) & main_licenses 1174 if obsolete: 1175 oe.qa.handle_error("obsolete-license", "Recipe LICENSE includes obsolete licenses %s" % ' '.join(obsolete), d) 1176 1177 bb.build.exec_func("read_subpackage_metadata", d) 1178 1179 # Check non UTF-8 characters on recipe's metadata 1180 package_qa_check_encoding(['DESCRIPTION', 'SUMMARY', 'LICENSE', 'SECTION'], 'utf-8', d) 1181 1182 logdir = d.getVar('T') 1183 pn = d.getVar('PN') 1184 1185 # Scan the packages... 1186 pkgdest = d.getVar('PKGDEST') 1187 packages = set((d.getVar('PACKAGES') or '').split()) 1188 1189 global pkgfiles 1190 pkgfiles = {} 1191 for pkg in packages: 1192 pkgfiles[pkg] = [] 1193 pkgdir = os.path.join(pkgdest, pkg) 1194 for walkroot, dirs, files in os.walk(pkgdir): 1195 # Don't walk into top-level CONTROL or DEBIAN directories as these 1196 # are temporary directories created by do_package. 1197 if walkroot == pkgdir: 1198 for control in ("CONTROL", "DEBIAN"): 1199 if control in dirs: 1200 dirs.remove(control) 1201 for file in files: 1202 pkgfiles[pkg].append(os.path.join(walkroot, file)) 1203 1204 # no packages should be scanned 1205 if not packages: 1206 return 1207 1208 import re 1209 # The package name matches the [a-z0-9.+-]+ regular expression 1210 pkgname_pattern = re.compile(r"^[a-z0-9.+-]+$") 1211 1212 taskdepdata = d.getVar("BB_TASKDEPDATA", False) 1213 taskdeps = set() 1214 for dep in taskdepdata: 1215 taskdeps.add(taskdepdata[dep][0]) 1216 1217 def parse_test_matrix(matrix_name): 1218 testmatrix = d.getVarFlags(matrix_name) or {} 1219 g = globals() 1220 warnchecks = [] 1221 for w in (d.getVar("WARN_QA") or "").split(): 1222 if w in skip: 1223 continue 1224 if w in testmatrix and testmatrix[w] in g: 1225 warnchecks.append(g[testmatrix[w]]) 1226 1227 errorchecks = [] 1228 for e in (d.getVar("ERROR_QA") or "").split(): 1229 if e in skip: 1230 continue 1231 if e in testmatrix and testmatrix[e] in g: 1232 errorchecks.append(g[testmatrix[e]]) 1233 return warnchecks, errorchecks 1234 1235 for package in packages: 1236 skip = set((d.getVar('INSANE_SKIP') or "").split() + 1237 (d.getVar('INSANE_SKIP:' + package) or "").split()) 1238 if skip: 1239 bb.note("Package %s skipping QA tests: %s" % (package, str(skip))) 1240 1241 bb.note("Checking Package: %s" % package) 1242 # Check package name 1243 if not pkgname_pattern.match(package): 1244 oe.qa.handle_error("pkgname", 1245 "%s doesn't match the [a-z0-9.+-]+ regex" % package, d) 1246 1247 warn_checks, error_checks = parse_test_matrix("QAPATHTEST") 1248 package_qa_walk(warn_checks, error_checks, package, d) 1249 1250 warn_checks, error_checks = parse_test_matrix("QAPKGTEST") 1251 package_qa_package(warn_checks, error_checks, package, d) 1252 1253 package_qa_check_rdepends(package, pkgdest, skip, taskdeps, packages, d) 1254 package_qa_check_deps(package, pkgdest, d) 1255 1256 warn_checks, error_checks = parse_test_matrix("QARECIPETEST") 1257 package_qa_recipe(warn_checks, error_checks, pn, d) 1258 1259 if 'libdir' in d.getVar("ALL_QA").split(): 1260 package_qa_check_libdir(d) 1261 1262 oe.qa.exit_if_errors(d) 1263} 1264 1265# binutils is used for most checks, so need to set as dependency 1266# POPULATESYSROOTDEPS is defined in staging class. 1267do_package_qa[depends] += "${POPULATESYSROOTDEPS}" 1268do_package_qa[vardeps] = "${@bb.utils.contains('ERROR_QA', 'empty-dirs', 'QA_EMPTY_DIRS', '', d)}" 1269do_package_qa[vardepsexclude] = "BB_TASKDEPDATA" 1270do_package_qa[rdeptask] = "do_packagedata" 1271addtask do_package_qa after do_packagedata do_package before do_build 1272 1273# Add the package specific INSANE_SKIPs to the sstate dependencies 1274python() { 1275 pkgs = (d.getVar('PACKAGES') or '').split() 1276 for pkg in pkgs: 1277 d.appendVarFlag("do_package_qa", "vardeps", " INSANE_SKIP:{}".format(pkg)) 1278} 1279 1280SSTATETASKS += "do_package_qa" 1281do_package_qa[sstate-inputdirs] = "" 1282do_package_qa[sstate-outputdirs] = "" 1283python do_package_qa_setscene () { 1284 sstate_setscene(d) 1285} 1286addtask do_package_qa_setscene 1287 1288python do_qa_sysroot() { 1289 bb.note("QA checking do_populate_sysroot") 1290 sysroot_destdir = d.expand('${SYSROOT_DESTDIR}') 1291 for sysroot_dir in d.expand('${SYSROOT_DIRS}').split(): 1292 qa_check_staged(sysroot_destdir + sysroot_dir, d) 1293 oe.qa.exit_with_message_if_errors("do_populate_sysroot for this recipe installed files with QA issues", d) 1294} 1295do_populate_sysroot[postfuncs] += "do_qa_sysroot" 1296 1297python do_qa_patch() { 1298 import subprocess 1299 1300 ########################################################################### 1301 # Check patch.log for fuzz warnings 1302 # 1303 # Further information on why we check for patch fuzz warnings: 1304 # http://lists.openembedded.org/pipermail/openembedded-core/2018-March/148675.html 1305 # https://bugzilla.yoctoproject.org/show_bug.cgi?id=10450 1306 ########################################################################### 1307 1308 logdir = d.getVar('T') 1309 patchlog = os.path.join(logdir,"log.do_patch") 1310 1311 if os.path.exists(patchlog): 1312 fuzzheader = '--- Patch fuzz start ---' 1313 fuzzfooter = '--- Patch fuzz end ---' 1314 statement = "grep -e '%s' %s > /dev/null" % (fuzzheader, patchlog) 1315 if subprocess.call(statement, shell=True) == 0: 1316 msg = "Fuzz detected:\n\n" 1317 fuzzmsg = "" 1318 inFuzzInfo = False 1319 f = open(patchlog, "r") 1320 for line in f: 1321 if fuzzheader in line: 1322 inFuzzInfo = True 1323 fuzzmsg = "" 1324 elif fuzzfooter in line: 1325 fuzzmsg = fuzzmsg.replace('\n\n', '\n') 1326 msg += fuzzmsg 1327 msg += "\n" 1328 inFuzzInfo = False 1329 elif inFuzzInfo and not 'Now at patch' in line: 1330 fuzzmsg += line 1331 f.close() 1332 msg += "The context lines in the patches can be updated with devtool:\n" 1333 msg += "\n" 1334 msg += " devtool modify %s\n" % d.getVar('PN') 1335 msg += " devtool finish --force-patch-refresh %s <layer_path>\n\n" % d.getVar('PN') 1336 msg += "Don't forget to review changes done by devtool!\n" 1337 msg += "\nPatch log indicates that patches do not apply cleanly." 1338 oe.qa.handle_error("patch-fuzz", msg, d) 1339 1340 # Check if the patch contains a correctly formatted and spelled Upstream-Status 1341 import re 1342 from oe import patch 1343 1344 for url in patch.src_patches(d): 1345 (_, _, fullpath, _, _, _) = bb.fetch.decodeurl(url) 1346 1347 msg = oe.qa.check_upstream_status(fullpath) 1348 if msg: 1349 oe.qa.handle_error("patch-status", msg, d) 1350 1351 oe.qa.exit_if_errors(d) 1352} 1353 1354python do_qa_configure() { 1355 import subprocess 1356 1357 ########################################################################### 1358 # Check config.log for cross compile issues 1359 ########################################################################### 1360 1361 configs = [] 1362 workdir = d.getVar('WORKDIR') 1363 1364 skip = (d.getVar('INSANE_SKIP') or "").split() 1365 skip_configure_unsafe = False 1366 if 'configure-unsafe' in skip: 1367 bb.note("Recipe %s skipping qa checking: configure-unsafe" % d.getVar('PN')) 1368 skip_configure_unsafe = True 1369 1370 if bb.data.inherits_class('autotools', d) and not skip_configure_unsafe: 1371 bb.note("Checking autotools environment for common misconfiguration") 1372 for root, dirs, files in os.walk(workdir): 1373 statement = "grep -q -F -e 'is unsafe for cross-compilation' %s" % \ 1374 os.path.join(root,"config.log") 1375 if "config.log" in files: 1376 if subprocess.call(statement, shell=True) == 0: 1377 error_msg = """This autoconf log indicates errors, it looked at host include and/or library paths while determining system capabilities. 1378Rerun configure task after fixing this.""" 1379 oe.qa.handle_error("configure-unsafe", error_msg, d) 1380 1381 if "configure.ac" in files: 1382 configs.append(os.path.join(root,"configure.ac")) 1383 if "configure.in" in files: 1384 configs.append(os.path.join(root, "configure.in")) 1385 1386 ########################################################################### 1387 # Check gettext configuration and dependencies are correct 1388 ########################################################################### 1389 1390 skip_configure_gettext = False 1391 if 'configure-gettext' in skip: 1392 bb.note("Recipe %s skipping qa checking: configure-gettext" % d.getVar('PN')) 1393 skip_configure_gettext = True 1394 1395 cnf = d.getVar('EXTRA_OECONF') or "" 1396 if not ("gettext" in d.getVar('P') or "gcc-runtime" in d.getVar('P') or \ 1397 "--disable-nls" in cnf or skip_configure_gettext): 1398 ml = d.getVar("MLPREFIX") or "" 1399 if bb.data.inherits_class('cross-canadian', d): 1400 gt = "nativesdk-gettext" 1401 else: 1402 gt = "gettext-native" 1403 deps = bb.utils.explode_deps(d.getVar('DEPENDS') or "") 1404 if gt not in deps: 1405 for config in configs: 1406 gnu = "grep \"^[[:space:]]*AM_GNU_GETTEXT\" %s >/dev/null" % config 1407 if subprocess.call(gnu, shell=True) == 0: 1408 error_msg = "AM_GNU_GETTEXT used but no inherit gettext" 1409 oe.qa.handle_error("configure-gettext", error_msg, d) 1410 1411 ########################################################################### 1412 # Check unrecognised configure options (with a white list) 1413 ########################################################################### 1414 if bb.data.inherits_class("autotools", d): 1415 bb.note("Checking configure output for unrecognised options") 1416 try: 1417 if bb.data.inherits_class("autotools", d): 1418 flag = "WARNING: unrecognized options:" 1419 log = os.path.join(d.getVar('B'), 'config.log') 1420 output = subprocess.check_output(['grep', '-F', flag, log]).decode("utf-8").replace(', ', ' ').replace('"', '') 1421 options = set() 1422 for line in output.splitlines(): 1423 options |= set(line.partition(flag)[2].split()) 1424 ignore_opts = set(d.getVar("UNKNOWN_CONFIGURE_OPT_IGNORE").split()) 1425 options -= ignore_opts 1426 if options: 1427 pn = d.getVar('PN') 1428 error_msg = pn + ": configure was passed unrecognised options: " + " ".join(options) 1429 oe.qa.handle_error("unknown-configure-option", error_msg, d) 1430 except subprocess.CalledProcessError: 1431 pass 1432 1433 # Check invalid PACKAGECONFIG 1434 pkgconfig = (d.getVar("PACKAGECONFIG") or "").split() 1435 if pkgconfig: 1436 pkgconfigflags = d.getVarFlags("PACKAGECONFIG") or {} 1437 for pconfig in pkgconfig: 1438 if pconfig not in pkgconfigflags: 1439 pn = d.getVar('PN') 1440 error_msg = "%s: invalid PACKAGECONFIG: %s" % (pn, pconfig) 1441 oe.qa.handle_error("invalid-packageconfig", error_msg, d) 1442 1443 oe.qa.exit_if_errors(d) 1444} 1445 1446def unpack_check_src_uri(pn, d): 1447 import re 1448 1449 skip = (d.getVar('INSANE_SKIP') or "").split() 1450 if 'src-uri-bad' in skip: 1451 bb.note("Recipe %s skipping qa checking: src-uri-bad" % d.getVar('PN')) 1452 return 1453 1454 if "${PN}" in d.getVar("SRC_URI", False): 1455 oe.qa.handle_error("src-uri-bad", "%s: SRC_URI uses PN not BPN" % pn, d) 1456 1457 for url in d.getVar("SRC_URI").split(): 1458 # Search for github and gitlab URLs that pull unstable archives (comment for future greppers) 1459 if re.search(r"git(hu|la)b\.com/.+/.+/archive/.+", url) or "//codeload.github.com/" in url: 1460 oe.qa.handle_error("src-uri-bad", "%s: SRC_URI uses unstable GitHub/GitLab archives, convert recipe to use git protocol" % pn, d) 1461 1462python do_qa_unpack() { 1463 src_uri = d.getVar('SRC_URI') 1464 s_dir = d.getVar('S') 1465 if src_uri and not os.path.exists(s_dir): 1466 bb.warn('%s: the directory %s (%s) pointed to by the S variable doesn\'t exist - please set S within the recipe to point to where the source has been unpacked to' % (d.getVar('PN'), d.getVar('S', False), s_dir)) 1467 1468 unpack_check_src_uri(d.getVar('PN'), d) 1469} 1470 1471python do_recipe_qa() { 1472 def test_missing_metadata(d): 1473 fn = d.getVar("FILE") 1474 pn = d.getVar('BPN') 1475 srcfile = d.getVar('SRC_URI').split() 1476 # Check that SUMMARY is not the same as the default from bitbake.conf 1477 if d.getVar('SUMMARY') == d.expand("${PN} version ${PV}-${PR}"): 1478 oe.qa.handle_error("missing-metadata", "Recipe {} in {} does not contain a SUMMARY. Please add an entry.".format(pn, fn), d) 1479 if not d.getVar('HOMEPAGE'): 1480 if srcfile and srcfile[0].startswith('file') or not d.getVar('SRC_URI'): 1481 # We are only interested in recipes SRC_URI fetched from external sources 1482 pass 1483 else: 1484 oe.qa.handle_error("missing-metadata", "Recipe {} in {} does not contain a HOMEPAGE. Please add an entry.".format(pn, fn), d) 1485 1486 def test_missing_maintainer(d): 1487 fn = d.getVar("FILE") 1488 pn = d.getVar("PN") 1489 if pn.endswith("-native") or pn.startswith("nativesdk-") or "packagegroup-" in pn or "core-image-ptest-" in pn: 1490 return 1491 if not d.getVar('RECIPE_MAINTAINER'): 1492 oe.qa.handle_error("missing-maintainer", "Recipe {} in {} does not have an assigned maintainer. Please add an entry into meta/conf/distro/include/maintainers.inc.".format(pn, fn), d) 1493 1494 test_missing_metadata(d) 1495 test_missing_maintainer(d) 1496 oe.qa.exit_if_errors(d) 1497} 1498 1499addtask do_recipe_qa before do_fetch do_package_qa do_build 1500 1501SSTATETASKS += "do_recipe_qa" 1502do_recipe_qa[sstate-inputdirs] = "" 1503do_recipe_qa[sstate-outputdirs] = "" 1504python do_recipe_qa_setscene () { 1505 sstate_setscene(d) 1506} 1507addtask do_recipe_qa_setscene 1508 1509# Check for patch fuzz 1510do_patch[postfuncs] += "do_qa_patch " 1511 1512# Check broken config.log files, for packages requiring Gettext which 1513# don't have it in DEPENDS. 1514#addtask qa_configure after do_configure before do_compile 1515do_configure[postfuncs] += "do_qa_configure " 1516 1517# Check does S exist. 1518do_unpack[postfuncs] += "do_qa_unpack" 1519 1520python () { 1521 import re 1522 1523 tests = d.getVar('ALL_QA').split() 1524 if "desktop" in tests: 1525 d.appendVar("PACKAGE_DEPENDS", " desktop-file-utils-native") 1526 1527 ########################################################################### 1528 # Check various variables 1529 ########################################################################### 1530 1531 # Checking ${FILESEXTRAPATHS} 1532 extrapaths = (d.getVar("FILESEXTRAPATHS") or "") 1533 if '__default' not in extrapaths.split(":"): 1534 msg = "FILESEXTRAPATHS-variable, must always use :prepend (or :append)\n" 1535 msg += "type of assignment, and don't forget the colon.\n" 1536 msg += "Please assign it with the format of:\n" 1537 msg += " FILESEXTRAPATHS:append := \":${THISDIR}/Your_Files_Path\" or\n" 1538 msg += " FILESEXTRAPATHS:prepend := \"${THISDIR}/Your_Files_Path:\"\n" 1539 msg += "in your bbappend file\n\n" 1540 msg += "Your incorrect assignment is:\n" 1541 msg += "%s\n" % extrapaths 1542 bb.warn(msg) 1543 1544 overrides = d.getVar('OVERRIDES').split(':') 1545 pn = d.getVar('PN') 1546 if pn in overrides: 1547 msg = 'Recipe %s has PN of "%s" which is in OVERRIDES, this can result in unexpected behaviour.' % (d.getVar("FILE"), pn) 1548 oe.qa.handle_error("pn-overrides", msg, d) 1549 prog = re.compile(r'[A-Z]') 1550 if prog.search(pn): 1551 oe.qa.handle_error("uppercase-pn", 'PN: %s is upper case, this can result in unexpected behavior.' % pn, d) 1552 1553 # Some people mistakenly use DEPENDS:${PN} instead of DEPENDS and wonder 1554 # why it doesn't work. 1555 if (d.getVar(d.expand('DEPENDS:${PN}'))): 1556 oe.qa.handle_error("pkgvarcheck", "recipe uses DEPENDS:${PN}, should use DEPENDS", d) 1557 1558 issues = [] 1559 if (d.getVar('PACKAGES') or "").split(): 1560 for dep in (d.getVar('QADEPENDS') or "").split(): 1561 d.appendVarFlag('do_package_qa', 'depends', " %s:do_populate_sysroot" % dep) 1562 for var in 'RDEPENDS', 'RRECOMMENDS', 'RSUGGESTS', 'RCONFLICTS', 'RPROVIDES', 'RREPLACES', 'FILES', 'pkg_preinst', 'pkg_postinst', 'pkg_prerm', 'pkg_postrm', 'ALLOW_EMPTY': 1563 if d.getVar(var, False): 1564 issues.append(var) 1565 1566 fakeroot_tests = d.getVar('FAKEROOT_QA').split() 1567 if set(tests) & set(fakeroot_tests): 1568 d.setVarFlag('do_package_qa', 'fakeroot', '1') 1569 d.appendVarFlag('do_package_qa', 'depends', ' virtual/fakeroot-native:do_populate_sysroot') 1570 else: 1571 d.setVarFlag('do_package_qa', 'rdeptask', '') 1572 for i in issues: 1573 oe.qa.handle_error("pkgvarcheck", "%s: Variable %s is set as not being package specific, please fix this." % (d.getVar("FILE"), i), d) 1574 1575 if 'native-last' not in (d.getVar('INSANE_SKIP') or "").split(): 1576 for native_class in ['native', 'nativesdk']: 1577 if bb.data.inherits_class(native_class, d): 1578 1579 inherited_classes = d.getVar('__inherit_cache', False) or [] 1580 needle = "/" + native_class 1581 1582 bbclassextend = (d.getVar('BBCLASSEXTEND') or '').split() 1583 # BBCLASSEXTEND items are always added in the end 1584 skip_classes = bbclassextend 1585 if bb.data.inherits_class('native', d) or 'native' in bbclassextend: 1586 # native also inherits nopackages and relocatable bbclasses 1587 skip_classes.extend(['nopackages', 'relocatable']) 1588 1589 broken_order = [] 1590 for class_item in reversed(inherited_classes): 1591 if needle not in class_item: 1592 for extend_item in skip_classes: 1593 if '/%s.bbclass' % extend_item in class_item: 1594 break 1595 else: 1596 pn = d.getVar('PN') 1597 broken_order.append(os.path.basename(class_item)) 1598 else: 1599 break 1600 if broken_order: 1601 oe.qa.handle_error("native-last", "%s: native/nativesdk class is not inherited last, this can result in unexpected behaviour. " 1602 "Classes inherited after native/nativesdk: %s" % (pn, " ".join(broken_order)), d) 1603 1604 oe.qa.exit_if_errors(d) 1605} 1606