xref: /OK3568_Linux_fs/yocto/poky/meta/lib/oe/sstatesig.py (revision 4882a59341e53eb6f0b4789bf948001014eff981)
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