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