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