1# 2# Copyright OpenEmbedded Contributors 3# 4# SPDX-License-Identifier: GPL-2.0-only 5# 6import bb.siggen 7import bb.runqueue 8import oe 9 10def sstate_rundepfilter(siggen, fn, recipename, task, dep, depname, dataCaches): 11 # Return True if we should keep the dependency, False to drop it 12 def isNative(x): 13 return x.endswith("-native") 14 def isCross(x): 15 return "-cross-" in x 16 def isNativeSDK(x): 17 return x.startswith("nativesdk-") 18 def isKernel(mc, fn): 19 inherits = " ".join(dataCaches[mc].inherits[fn]) 20 return inherits.find("/module-base.bbclass") != -1 or inherits.find("/linux-kernel-base.bbclass") != -1 21 def isPackageGroup(mc, fn): 22 inherits = " ".join(dataCaches[mc].inherits[fn]) 23 return "/packagegroup.bbclass" in inherits 24 def isAllArch(mc, fn): 25 inherits = " ".join(dataCaches[mc].inherits[fn]) 26 return "/allarch.bbclass" in inherits 27 def isImage(mc, fn): 28 return "/image.bbclass" in " ".join(dataCaches[mc].inherits[fn]) 29 def isSPDXTask(task): 30 return task in ("do_create_spdx", "do_create_runtime_spdx") 31 32 depmc, _, deptaskname, depmcfn = bb.runqueue.split_tid_mcfn(dep) 33 mc, _ = bb.runqueue.split_mc(fn) 34 35 # Keep all dependencies between SPDX tasks in the signature. SPDX documents 36 # are linked together by hashes, which means if a dependent document changes, 37 # all downstream documents must be re-written (even if they are "safe" 38 # dependencies). 39 if isSPDXTask(task) and isSPDXTask(deptaskname): 40 return True 41 42 # (Almost) always include our own inter-task dependencies (unless it comes 43 # from a mcdepends). The exception is the special 44 # do_kernel_configme->do_unpack_and_patch dependency from archiver.bbclass. 45 if recipename == depname and depmc == mc: 46 if task == "do_kernel_configme" and deptaskname == "do_unpack_and_patch": 47 return False 48 return True 49 50 # Exclude well defined recipe->dependency 51 if "%s->%s" % (recipename, depname) in siggen.saferecipedeps: 52 return False 53 54 # Check for special wildcard 55 if "*->%s" % depname in siggen.saferecipedeps and recipename != depname: 56 return False 57 58 # Don't change native/cross/nativesdk recipe dependencies any further 59 if isNative(recipename) or isCross(recipename) or isNativeSDK(recipename): 60 return True 61 62 # Only target packages beyond here 63 64 # allarch packagegroups are assumed to have well behaved names which don't change between architecures/tunes 65 if isPackageGroup(mc, fn) and isAllArch(mc, fn) and not isNative(depname): 66 return False 67 68 # Exclude well defined machine specific configurations which don't change ABI 69 if depname in siggen.abisaferecipes and not isImage(mc, fn): 70 return False 71 72 # Kernel modules are well namespaced. We don't want to depend on the kernel's checksum 73 # if we're just doing an RRECOMMENDS:xxx = "kernel-module-*", not least because the checksum 74 # is machine specific. 75 # Therefore if we're not a kernel or a module recipe (inheriting the kernel classes) 76 # and we reccomend a kernel-module, we exclude the dependency. 77 if dataCaches and isKernel(depmc, depmcfn) and not isKernel(mc, fn): 78 for pkg in dataCaches[mc].runrecs[fn]: 79 if " ".join(dataCaches[mc].runrecs[fn][pkg]).find("kernel-module-") != -1: 80 return False 81 82 # Default to keep dependencies 83 return True 84 85def sstate_lockedsigs(d): 86 sigs = {} 87 types = (d.getVar("SIGGEN_LOCKEDSIGS_TYPES") or "").split() 88 for t in types: 89 siggen_lockedsigs_var = "SIGGEN_LOCKEDSIGS_%s" % t 90 lockedsigs = (d.getVar(siggen_lockedsigs_var) or "").split() 91 for ls in lockedsigs: 92 pn, task, h = ls.split(":", 2) 93 if pn not in sigs: 94 sigs[pn] = {} 95 sigs[pn][task] = [h, siggen_lockedsigs_var] 96 return sigs 97 98class SignatureGeneratorOEBasic(bb.siggen.SignatureGeneratorBasic): 99 name = "OEBasic" 100 def init_rundepcheck(self, data): 101 self.abisaferecipes = (data.getVar("SIGGEN_EXCLUDERECIPES_ABISAFE") or "").split() 102 self.saferecipedeps = (data.getVar("SIGGEN_EXCLUDE_SAFE_RECIPE_DEPS") or "").split() 103 pass 104 def rundep_check(self, fn, recipename, task, dep, depname, dataCaches = None): 105 return sstate_rundepfilter(self, fn, recipename, task, dep, depname, dataCaches) 106 107class SignatureGeneratorOEBasicHashMixIn(object): 108 supports_multiconfig_datacaches = True 109 110 def init_rundepcheck(self, data): 111 self.abisaferecipes = (data.getVar("SIGGEN_EXCLUDERECIPES_ABISAFE") or "").split() 112 self.saferecipedeps = (data.getVar("SIGGEN_EXCLUDE_SAFE_RECIPE_DEPS") or "").split() 113 self.lockedsigs = sstate_lockedsigs(data) 114 self.lockedhashes = {} 115 self.lockedpnmap = {} 116 self.lockedhashfn = {} 117 self.machine = data.getVar("MACHINE") 118 self.mismatch_msgs = [] 119 self.unlockedrecipes = (data.getVar("SIGGEN_UNLOCKED_RECIPES") or 120 "").split() 121 self.unlockedrecipes = { k: "" for k in self.unlockedrecipes } 122 self._internal = False 123 pass 124 125 def tasks_resolved(self, virtmap, virtpnmap, dataCache): 126 # Translate virtual/xxx entries to PN values 127 newabisafe = [] 128 for a in self.abisaferecipes: 129 if a in virtpnmap: 130 newabisafe.append(virtpnmap[a]) 131 else: 132 newabisafe.append(a) 133 self.abisaferecipes = newabisafe 134 newsafedeps = [] 135 for a in self.saferecipedeps: 136 a1, a2 = a.split("->") 137 if a1 in virtpnmap: 138 a1 = virtpnmap[a1] 139 if a2 in virtpnmap: 140 a2 = virtpnmap[a2] 141 newsafedeps.append(a1 + "->" + a2) 142 self.saferecipedeps = newsafedeps 143 144 def rundep_check(self, fn, recipename, task, dep, depname, dataCaches = None): 145 return sstate_rundepfilter(self, fn, recipename, task, dep, depname, dataCaches) 146 147 def get_taskdata(self): 148 return (self.lockedpnmap, self.lockedhashfn, self.lockedhashes) + super().get_taskdata() 149 150 def set_taskdata(self, data): 151 self.lockedpnmap, self.lockedhashfn, self.lockedhashes = data[:3] 152 super().set_taskdata(data[3:]) 153 154 def dump_sigs(self, dataCache, options): 155 sigfile = os.getcwd() + "/locked-sigs.inc" 156 bb.plain("Writing locked sigs to %s" % sigfile) 157 self.dump_lockedsigs(sigfile) 158 return super(bb.siggen.SignatureGeneratorBasicHash, self).dump_sigs(dataCache, options) 159 160 161 def get_taskhash(self, tid, deps, dataCaches): 162 if tid in self.lockedhashes: 163 if self.lockedhashes[tid]: 164 return self.lockedhashes[tid] 165 else: 166 return super().get_taskhash(tid, deps, dataCaches) 167 168 h = super().get_taskhash(tid, deps, dataCaches) 169 170 (mc, _, task, fn) = bb.runqueue.split_tid_mcfn(tid) 171 172 recipename = dataCaches[mc].pkg_fn[fn] 173 self.lockedpnmap[fn] = recipename 174 self.lockedhashfn[fn] = dataCaches[mc].hashfn[fn] 175 176 unlocked = False 177 if recipename in self.unlockedrecipes: 178 unlocked = True 179 else: 180 def recipename_from_dep(dep): 181 (depmc, _, _, depfn) = bb.runqueue.split_tid_mcfn(dep) 182 return dataCaches[depmc].pkg_fn[depfn] 183 184 # If any unlocked recipe is in the direct dependencies then the 185 # current recipe should be unlocked as well. 186 depnames = [ recipename_from_dep(x) for x in deps if mc == bb.runqueue.mc_from_tid(x)] 187 if any(x in y for y in depnames for x in self.unlockedrecipes): 188 self.unlockedrecipes[recipename] = '' 189 unlocked = True 190 191 if not unlocked and recipename in self.lockedsigs: 192 if task in self.lockedsigs[recipename]: 193 h_locked = self.lockedsigs[recipename][task][0] 194 var = self.lockedsigs[recipename][task][1] 195 self.lockedhashes[tid] = h_locked 196 self._internal = True 197 unihash = self.get_unihash(tid) 198 self._internal = False 199 #bb.warn("Using %s %s %s" % (recipename, task, h)) 200 201 if h != h_locked and h_locked != unihash: 202 self.mismatch_msgs.append('The %s:%s sig is computed to be %s, but the sig is locked to %s in %s' 203 % (recipename, task, h, h_locked, var)) 204 205 return h_locked 206 207 self.lockedhashes[tid] = False 208 #bb.warn("%s %s %s" % (recipename, task, h)) 209 return h 210 211 def get_stampfile_hash(self, tid): 212 if tid in self.lockedhashes and self.lockedhashes[tid]: 213 return self.lockedhashes[tid] 214 return super().get_stampfile_hash(tid) 215 216 def get_unihash(self, tid): 217 if tid in self.lockedhashes and self.lockedhashes[tid] and not self._internal: 218 return self.lockedhashes[tid] 219 return super().get_unihash(tid) 220 221 def dump_sigtask(self, fn, task, stampbase, runtime): 222 tid = fn + ":" + task 223 if tid in self.lockedhashes and self.lockedhashes[tid]: 224 return 225 super(bb.siggen.SignatureGeneratorBasicHash, self).dump_sigtask(fn, task, stampbase, runtime) 226 227 def dump_lockedsigs(self, sigfile, taskfilter=None): 228 types = {} 229 for tid in self.runtaskdeps: 230 if taskfilter: 231 if not tid in taskfilter: 232 continue 233 fn = bb.runqueue.fn_from_tid(tid) 234 t = self.lockedhashfn[fn].split(" ")[1].split(":")[5] 235 t = 't-' + t.replace('_', '-') 236 if t not in types: 237 types[t] = [] 238 types[t].append(tid) 239 240 with open(sigfile, "w") as f: 241 l = sorted(types) 242 for t in l: 243 f.write('SIGGEN_LOCKEDSIGS_%s = "\\\n' % t) 244 types[t].sort() 245 sortedtid = sorted(types[t], key=lambda tid: self.lockedpnmap[bb.runqueue.fn_from_tid(tid)]) 246 for tid in sortedtid: 247 (_, _, task, fn) = bb.runqueue.split_tid_mcfn(tid) 248 if tid not in self.taskhash: 249 continue 250 f.write(" " + self.lockedpnmap[fn] + ":" + task + ":" + self.get_unihash(tid) + " \\\n") 251 f.write(' "\n') 252 f.write('SIGGEN_LOCKEDSIGS_TYPES:%s = "%s"' % (self.machine, " ".join(l))) 253 254 def dump_siglist(self, sigfile, path_prefix_strip=None): 255 def strip_fn(fn): 256 nonlocal path_prefix_strip 257 if not path_prefix_strip: 258 return fn 259 260 fn_exp = fn.split(":") 261 if fn_exp[-1].startswith(path_prefix_strip): 262 fn_exp[-1] = fn_exp[-1][len(path_prefix_strip):] 263 264 return ":".join(fn_exp) 265 266 with open(sigfile, "w") as f: 267 tasks = [] 268 for taskitem in self.taskhash: 269 (fn, task) = taskitem.rsplit(":", 1) 270 pn = self.lockedpnmap[fn] 271 tasks.append((pn, task, strip_fn(fn), self.taskhash[taskitem])) 272 for (pn, task, fn, taskhash) in sorted(tasks): 273 f.write('%s:%s %s %s\n' % (pn, task, fn, taskhash)) 274 275 def checkhashes(self, sq_data, missed, found, d): 276 warn_msgs = [] 277 error_msgs = [] 278 sstate_missing_msgs = [] 279 280 for tid in sq_data['hash']: 281 if tid not in found: 282 for pn in self.lockedsigs: 283 taskname = bb.runqueue.taskname_from_tid(tid) 284 if sq_data['hash'][tid] in iter(self.lockedsigs[pn].values()): 285 if taskname == 'do_shared_workdir': 286 continue 287 sstate_missing_msgs.append("Locked sig is set for %s:%s (%s) yet not in sstate cache?" 288 % (pn, taskname, sq_data['hash'][tid])) 289 290 checklevel = d.getVar("SIGGEN_LOCKEDSIGS_TASKSIG_CHECK") 291 if checklevel == 'warn': 292 warn_msgs += self.mismatch_msgs 293 elif checklevel == 'error': 294 error_msgs += self.mismatch_msgs 295 296 checklevel = d.getVar("SIGGEN_LOCKEDSIGS_SSTATE_EXISTS_CHECK") 297 if checklevel == 'warn': 298 warn_msgs += sstate_missing_msgs 299 elif checklevel == 'error': 300 error_msgs += sstate_missing_msgs 301 302 if warn_msgs: 303 bb.warn("\n".join(warn_msgs)) 304 if error_msgs: 305 bb.fatal("\n".join(error_msgs)) 306 307class SignatureGeneratorOEBasicHash(SignatureGeneratorOEBasicHashMixIn, bb.siggen.SignatureGeneratorBasicHash): 308 name = "OEBasicHash" 309 310class SignatureGeneratorOEEquivHash(SignatureGeneratorOEBasicHashMixIn, bb.siggen.SignatureGeneratorUniHashMixIn, bb.siggen.SignatureGeneratorBasicHash): 311 name = "OEEquivHash" 312 313 def init_rundepcheck(self, data): 314 super().init_rundepcheck(data) 315 self.server = data.getVar('BB_HASHSERVE') 316 if not self.server: 317 bb.fatal("OEEquivHash requires BB_HASHSERVE to be set") 318 self.method = data.getVar('SSTATE_HASHEQUIV_METHOD') 319 if not self.method: 320 bb.fatal("OEEquivHash requires SSTATE_HASHEQUIV_METHOD to be set") 321 322# Insert these classes into siggen's namespace so it can see and select them 323bb.siggen.SignatureGeneratorOEBasic = SignatureGeneratorOEBasic 324bb.siggen.SignatureGeneratorOEBasicHash = SignatureGeneratorOEBasicHash 325bb.siggen.SignatureGeneratorOEEquivHash = SignatureGeneratorOEEquivHash 326 327 328def find_siginfo(pn, taskname, taskhashlist, d): 329 """ Find signature data files for comparison purposes """ 330 331 import fnmatch 332 import glob 333 334 if not taskname: 335 # We have to derive pn and taskname 336 key = pn 337 splitit = key.split('.bb:') 338 taskname = splitit[1] 339 pn = os.path.basename(splitit[0]).split('_')[0] 340 if key.startswith('virtual:native:'): 341 pn = pn + '-native' 342 343 hashfiles = {} 344 filedates = {} 345 346 def get_hashval(siginfo): 347 if siginfo.endswith('.siginfo'): 348 return siginfo.rpartition(':')[2].partition('_')[0] 349 else: 350 return siginfo.rpartition('.')[2] 351 352 # First search in stamps dir 353 localdata = d.createCopy() 354 localdata.setVar('MULTIMACH_TARGET_SYS', '*') 355 localdata.setVar('PN', pn) 356 localdata.setVar('PV', '*') 357 localdata.setVar('PR', '*') 358 localdata.setVar('EXTENDPE', '') 359 stamp = localdata.getVar('STAMP') 360 if pn.startswith("gcc-source"): 361 # gcc-source shared workdir is a special case :( 362 stamp = localdata.expand("${STAMPS_DIR}/work-shared/gcc-${PV}-${PR}") 363 364 filespec = '%s.%s.sigdata.*' % (stamp, taskname) 365 foundall = False 366 import glob 367 for fullpath in glob.glob(filespec): 368 match = False 369 if taskhashlist: 370 for taskhash in taskhashlist: 371 if fullpath.endswith('.%s' % taskhash): 372 hashfiles[taskhash] = fullpath 373 if len(hashfiles) == len(taskhashlist): 374 foundall = True 375 break 376 else: 377 try: 378 filedates[fullpath] = os.stat(fullpath).st_mtime 379 except OSError: 380 continue 381 hashval = get_hashval(fullpath) 382 hashfiles[hashval] = fullpath 383 384 if not taskhashlist or (len(filedates) < 2 and not foundall): 385 # That didn't work, look in sstate-cache 386 hashes = taskhashlist or ['?' * 64] 387 localdata = bb.data.createCopy(d) 388 for hashval in hashes: 389 localdata.setVar('PACKAGE_ARCH', '*') 390 localdata.setVar('TARGET_VENDOR', '*') 391 localdata.setVar('TARGET_OS', '*') 392 localdata.setVar('PN', pn) 393 localdata.setVar('PV', '*') 394 localdata.setVar('PR', '*') 395 localdata.setVar('BB_TASKHASH', hashval) 396 localdata.setVar('SSTATE_CURRTASK', taskname[3:]) 397 swspec = localdata.getVar('SSTATE_SWSPEC') 398 if taskname in ['do_fetch', 'do_unpack', 'do_patch', 'do_populate_lic', 'do_preconfigure'] and swspec: 399 localdata.setVar('SSTATE_PKGSPEC', '${SSTATE_SWSPEC}') 400 elif pn.endswith('-native') or "-cross-" in pn or "-crosssdk-" in pn: 401 localdata.setVar('SSTATE_EXTRAPATH', "${NATIVELSBSTRING}/") 402 filespec = '%s.siginfo' % localdata.getVar('SSTATE_PKG') 403 404 matchedfiles = glob.glob(filespec) 405 for fullpath in matchedfiles: 406 actual_hashval = get_hashval(fullpath) 407 if actual_hashval in hashfiles: 408 continue 409 hashfiles[hashval] = fullpath 410 if not taskhashlist: 411 try: 412 filedates[fullpath] = os.stat(fullpath).st_mtime 413 except: 414 continue 415 416 if taskhashlist: 417 return hashfiles 418 else: 419 return filedates 420 421bb.siggen.find_siginfo = find_siginfo 422 423 424def sstate_get_manifest_filename(task, d): 425 """ 426 Return the sstate manifest file path for a particular task. 427 Also returns the datastore that can be used to query related variables. 428 """ 429 d2 = d.createCopy() 430 extrainf = d.getVarFlag("do_" + task, 'stamp-extra-info') 431 if extrainf: 432 d2.setVar("SSTATE_MANMACH", extrainf) 433 return (d2.expand("${SSTATE_MANFILEPREFIX}.%s" % task), d2) 434 435def find_sstate_manifest(taskdata, taskdata2, taskname, d, multilibcache): 436 d2 = d 437 variant = '' 438 curr_variant = '' 439 if d.getVar("BBEXTENDCURR") == "multilib": 440 curr_variant = d.getVar("BBEXTENDVARIANT") 441 if "virtclass-multilib" not in d.getVar("OVERRIDES"): 442 curr_variant = "invalid" 443 if taskdata2.startswith("virtual:multilib"): 444 variant = taskdata2.split(":")[2] 445 if curr_variant != variant: 446 if variant not in multilibcache: 447 multilibcache[variant] = oe.utils.get_multilib_datastore(variant, d) 448 d2 = multilibcache[variant] 449 450 if taskdata.endswith("-native"): 451 pkgarchs = ["${BUILD_ARCH}", "${BUILD_ARCH}_${ORIGNATIVELSBSTRING}"] 452 elif taskdata.startswith("nativesdk-"): 453 pkgarchs = ["${SDK_ARCH}_${SDK_OS}", "allarch"] 454 elif "-cross-canadian" in taskdata: 455 pkgarchs = ["${SDK_ARCH}_${SDK_ARCH}-${SDKPKGSUFFIX}"] 456 elif "-cross-" in taskdata: 457 pkgarchs = ["${BUILD_ARCH}"] 458 elif "-crosssdk" in taskdata: 459 pkgarchs = ["${BUILD_ARCH}_${SDK_ARCH}_${SDK_OS}"] 460 else: 461 pkgarchs = ['${MACHINE_ARCH}'] 462 pkgarchs = pkgarchs + list(reversed(d2.getVar("PACKAGE_EXTRA_ARCHS").split())) 463 pkgarchs.append('allarch') 464 pkgarchs.append('${SDK_ARCH}_${SDK_ARCH}-${SDKPKGSUFFIX}') 465 466 for pkgarch in pkgarchs: 467 manifest = d2.expand("${SSTATE_MANIFESTS}/manifest-%s-%s.%s" % (pkgarch, taskdata, taskname)) 468 if os.path.exists(manifest): 469 return manifest, d2 470 bb.fatal("Manifest %s not found in %s (variant '%s')?" % (manifest, d2.expand(" ".join(pkgarchs)), variant)) 471 return None, d2 472 473def OEOuthashBasic(path, sigfile, task, d): 474 """ 475 Basic output hash function 476 477 Calculates the output hash of a task by hashing all output file metadata, 478 and file contents. 479 """ 480 import hashlib 481 import stat 482 import pwd 483 import grp 484 import re 485 import fnmatch 486 487 def update_hash(s): 488 s = s.encode('utf-8') 489 h.update(s) 490 if sigfile: 491 sigfile.write(s) 492 493 h = hashlib.sha256() 494 prev_dir = os.getcwd() 495 corebase = d.getVar("COREBASE") 496 tmpdir = d.getVar("TMPDIR") 497 include_owners = os.environ.get('PSEUDO_DISABLED') == '0' 498 if "package_write_" in task or task == "package_qa": 499 include_owners = False 500 include_timestamps = False 501 include_root = True 502 if task == "package": 503 include_timestamps = True 504 include_root = False 505 hash_version = d.getVar('HASHEQUIV_HASH_VERSION') 506 extra_sigdata = d.getVar("HASHEQUIV_EXTRA_SIGDATA") 507 508 filemaps = {} 509 for m in (d.getVar('SSTATE_HASHEQUIV_FILEMAP') or '').split(): 510 entry = m.split(":") 511 if len(entry) != 3 or entry[0] != task: 512 continue 513 filemaps.setdefault(entry[1], []) 514 filemaps[entry[1]].append(entry[2]) 515 516 try: 517 os.chdir(path) 518 basepath = os.path.normpath(path) 519 520 update_hash("OEOuthashBasic\n") 521 if hash_version: 522 update_hash(hash_version + "\n") 523 524 if extra_sigdata: 525 update_hash(extra_sigdata + "\n") 526 527 # It is only currently useful to get equivalent hashes for things that 528 # can be restored from sstate. Since the sstate object is named using 529 # SSTATE_PKGSPEC and the task name, those should be included in the 530 # output hash calculation. 531 update_hash("SSTATE_PKGSPEC=%s\n" % d.getVar('SSTATE_PKGSPEC')) 532 update_hash("task=%s\n" % task) 533 534 for root, dirs, files in os.walk('.', topdown=True): 535 # Sort directories to ensure consistent ordering when recursing 536 dirs.sort() 537 files.sort() 538 539 def process(path): 540 s = os.lstat(path) 541 542 if stat.S_ISDIR(s.st_mode): 543 update_hash('d') 544 elif stat.S_ISCHR(s.st_mode): 545 update_hash('c') 546 elif stat.S_ISBLK(s.st_mode): 547 update_hash('b') 548 elif stat.S_ISSOCK(s.st_mode): 549 update_hash('s') 550 elif stat.S_ISLNK(s.st_mode): 551 update_hash('l') 552 elif stat.S_ISFIFO(s.st_mode): 553 update_hash('p') 554 else: 555 update_hash('-') 556 557 def add_perm(mask, on, off='-'): 558 if mask & s.st_mode: 559 update_hash(on) 560 else: 561 update_hash(off) 562 563 add_perm(stat.S_IRUSR, 'r') 564 add_perm(stat.S_IWUSR, 'w') 565 if stat.S_ISUID & s.st_mode: 566 add_perm(stat.S_IXUSR, 's', 'S') 567 else: 568 add_perm(stat.S_IXUSR, 'x') 569 570 if include_owners: 571 # Group/other permissions are only relevant in pseudo context 572 add_perm(stat.S_IRGRP, 'r') 573 add_perm(stat.S_IWGRP, 'w') 574 if stat.S_ISGID & s.st_mode: 575 add_perm(stat.S_IXGRP, 's', 'S') 576 else: 577 add_perm(stat.S_IXGRP, 'x') 578 579 add_perm(stat.S_IROTH, 'r') 580 add_perm(stat.S_IWOTH, 'w') 581 if stat.S_ISVTX & s.st_mode: 582 update_hash('t') 583 else: 584 add_perm(stat.S_IXOTH, 'x') 585 586 try: 587 update_hash(" %10s" % pwd.getpwuid(s.st_uid).pw_name) 588 update_hash(" %10s" % grp.getgrgid(s.st_gid).gr_name) 589 except KeyError as e: 590 bb.warn("KeyError in %s" % path) 591 msg = ("KeyError: %s\nPath %s is owned by uid %d, gid %d, which doesn't match " 592 "any user/group on target. This may be due to host contamination." % (e, path, s.st_uid, s.st_gid)) 593 raise Exception(msg).with_traceback(e.__traceback__) 594 595 if include_timestamps: 596 update_hash(" %10d" % s.st_mtime) 597 598 update_hash(" ") 599 if stat.S_ISBLK(s.st_mode) or stat.S_ISCHR(s.st_mode): 600 update_hash("%9s" % ("%d.%d" % (os.major(s.st_rdev), os.minor(s.st_rdev)))) 601 else: 602 update_hash(" " * 9) 603 604 filterfile = False 605 for entry in filemaps: 606 if fnmatch.fnmatch(path, entry): 607 filterfile = True 608 609 update_hash(" ") 610 if stat.S_ISREG(s.st_mode) and not filterfile: 611 update_hash("%10d" % s.st_size) 612 else: 613 update_hash(" " * 10) 614 615 update_hash(" ") 616 fh = hashlib.sha256() 617 if stat.S_ISREG(s.st_mode): 618 # Hash file contents 619 if filterfile: 620 # Need to ignore paths in crossscripts and postinst-useradd files. 621 with open(path, 'rb') as d: 622 chunk = d.read() 623 chunk = chunk.replace(bytes(basepath, encoding='utf8'), b'') 624 for entry in filemaps: 625 if not fnmatch.fnmatch(path, entry): 626 continue 627 for r in filemaps[entry]: 628 if r.startswith("regex-"): 629 chunk = re.sub(bytes(r[6:], encoding='utf8'), b'', chunk) 630 else: 631 chunk = chunk.replace(bytes(r, encoding='utf8'), b'') 632 fh.update(chunk) 633 else: 634 with open(path, 'rb') as d: 635 for chunk in iter(lambda: d.read(4096), b""): 636 fh.update(chunk) 637 update_hash(fh.hexdigest()) 638 else: 639 update_hash(" " * len(fh.hexdigest())) 640 641 update_hash(" %s" % path) 642 643 if stat.S_ISLNK(s.st_mode): 644 update_hash(" -> %s" % os.readlink(path)) 645 646 update_hash("\n") 647 648 # Process this directory and all its child files 649 if include_root or root != ".": 650 process(root) 651 for f in files: 652 if f == 'fixmepath': 653 continue 654 process(os.path.join(root, f)) 655 finally: 656 os.chdir(prev_dir) 657 658 return h.hexdigest() 659 660 661