sstatesig.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568
  1. #
  2. # SPDX-License-Identifier: GPL-2.0-only
  3. #
  4. import bb.siggen
  5. import oe
  6. def sstate_rundepfilter(siggen, fn, recipename, task, dep, depname, dataCache):
  7. # Return True if we should keep the dependency, False to drop it
  8. def isNative(x):
  9. return x.endswith("-native")
  10. def isCross(x):
  11. return "-cross-" in x
  12. def isNativeSDK(x):
  13. return x.startswith("nativesdk-")
  14. def isKernel(fn):
  15. inherits = " ".join(dataCache.inherits[fn])
  16. return inherits.find("/module-base.bbclass") != -1 or inherits.find("/linux-kernel-base.bbclass") != -1
  17. def isPackageGroup(fn):
  18. inherits = " ".join(dataCache.inherits[fn])
  19. return "/packagegroup.bbclass" in inherits
  20. def isAllArch(fn):
  21. inherits = " ".join(dataCache.inherits[fn])
  22. return "/allarch.bbclass" in inherits
  23. def isImage(fn):
  24. return "/image.bbclass" in " ".join(dataCache.inherits[fn])
  25. # (Almost) always include our own inter-task dependencies.
  26. # The exception is the special do_kernel_configme->do_unpack_and_patch
  27. # dependency from archiver.bbclass.
  28. if recipename == depname:
  29. if task == "do_kernel_configme" and dep.endswith(".do_unpack_and_patch"):
  30. return False
  31. return True
  32. # Exclude well defined recipe->dependency
  33. if "%s->%s" % (recipename, depname) in siggen.saferecipedeps:
  34. return False
  35. # Check for special wildcard
  36. if "*->%s" % depname in siggen.saferecipedeps and recipename != depname:
  37. return False
  38. # Don't change native/cross/nativesdk recipe dependencies any further
  39. if isNative(recipename) or isCross(recipename) or isNativeSDK(recipename):
  40. return True
  41. # Only target packages beyond here
  42. # allarch packagegroups are assumed to have well behaved names which don't change between architecures/tunes
  43. if isPackageGroup(fn) and isAllArch(fn) and not isNative(depname):
  44. return False
  45. # Exclude well defined machine specific configurations which don't change ABI
  46. if depname in siggen.abisaferecipes and not isImage(fn):
  47. return False
  48. # Kernel modules are well namespaced. We don't want to depend on the kernel's checksum
  49. # if we're just doing an RRECOMMENDS_xxx = "kernel-module-*", not least because the checksum
  50. # is machine specific.
  51. # Therefore if we're not a kernel or a module recipe (inheriting the kernel classes)
  52. # and we reccomend a kernel-module, we exclude the dependency.
  53. depfn = dep.rsplit(":", 1)[0]
  54. if dataCache and isKernel(depfn) and not isKernel(fn):
  55. for pkg in dataCache.runrecs[fn]:
  56. if " ".join(dataCache.runrecs[fn][pkg]).find("kernel-module-") != -1:
  57. return False
  58. # Default to keep dependencies
  59. return True
  60. def sstate_lockedsigs(d):
  61. sigs = {}
  62. types = (d.getVar("SIGGEN_LOCKEDSIGS_TYPES") or "").split()
  63. for t in types:
  64. siggen_lockedsigs_var = "SIGGEN_LOCKEDSIGS_%s" % t
  65. lockedsigs = (d.getVar(siggen_lockedsigs_var) or "").split()
  66. for ls in lockedsigs:
  67. pn, task, h = ls.split(":", 2)
  68. if pn not in sigs:
  69. sigs[pn] = {}
  70. sigs[pn][task] = [h, siggen_lockedsigs_var]
  71. return sigs
  72. class SignatureGeneratorOEBasic(bb.siggen.SignatureGeneratorBasic):
  73. name = "OEBasic"
  74. def init_rundepcheck(self, data):
  75. self.abisaferecipes = (data.getVar("SIGGEN_EXCLUDERECIPES_ABISAFE") or "").split()
  76. self.saferecipedeps = (data.getVar("SIGGEN_EXCLUDE_SAFE_RECIPE_DEPS") or "").split()
  77. pass
  78. def rundep_check(self, fn, recipename, task, dep, depname, dataCache = None):
  79. return sstate_rundepfilter(self, fn, recipename, task, dep, depname, dataCache)
  80. class SignatureGeneratorOEBasicHashMixIn(object):
  81. def init_rundepcheck(self, data):
  82. self.abisaferecipes = (data.getVar("SIGGEN_EXCLUDERECIPES_ABISAFE") or "").split()
  83. self.saferecipedeps = (data.getVar("SIGGEN_EXCLUDE_SAFE_RECIPE_DEPS") or "").split()
  84. self.lockedsigs = sstate_lockedsigs(data)
  85. self.lockedhashes = {}
  86. self.lockedpnmap = {}
  87. self.lockedhashfn = {}
  88. self.machine = data.getVar("MACHINE")
  89. self.mismatch_msgs = []
  90. self.unlockedrecipes = (data.getVar("SIGGEN_UNLOCKED_RECIPES") or
  91. "").split()
  92. self.unlockedrecipes = { k: "" for k in self.unlockedrecipes }
  93. pass
  94. def tasks_resolved(self, virtmap, virtpnmap, dataCache):
  95. # Translate virtual/xxx entries to PN values
  96. newabisafe = []
  97. for a in self.abisaferecipes:
  98. if a in virtpnmap:
  99. newabisafe.append(virtpnmap[a])
  100. else:
  101. newabisafe.append(a)
  102. self.abisaferecipes = newabisafe
  103. newsafedeps = []
  104. for a in self.saferecipedeps:
  105. a1, a2 = a.split("->")
  106. if a1 in virtpnmap:
  107. a1 = virtpnmap[a1]
  108. if a2 in virtpnmap:
  109. a2 = virtpnmap[a2]
  110. newsafedeps.append(a1 + "->" + a2)
  111. self.saferecipedeps = newsafedeps
  112. def rundep_check(self, fn, recipename, task, dep, depname, dataCache = None):
  113. return sstate_rundepfilter(self, fn, recipename, task, dep, depname, dataCache)
  114. def get_taskdata(self):
  115. return (self.lockedpnmap, self.lockedhashfn, self.lockedhashes) + super().get_taskdata()
  116. def set_taskdata(self, data):
  117. self.lockedpnmap, self.lockedhashfn, self.lockedhashes = data[:3]
  118. super().set_taskdata(data[3:])
  119. def dump_sigs(self, dataCache, options):
  120. sigfile = os.getcwd() + "/locked-sigs.inc"
  121. bb.plain("Writing locked sigs to %s" % sigfile)
  122. self.dump_lockedsigs(sigfile)
  123. return super(bb.siggen.SignatureGeneratorBasicHash, self).dump_sigs(dataCache, options)
  124. def get_taskhash(self, tid, deps, dataCache):
  125. h = super(bb.siggen.SignatureGeneratorBasicHash, self).get_taskhash(tid, deps, dataCache)
  126. if tid in self.lockedhashes:
  127. if self.lockedhashes[tid]:
  128. return self.lockedhashes[tid]
  129. else:
  130. return h
  131. h = super(bb.siggen.SignatureGeneratorBasicHash, self).get_taskhash(tid, deps, dataCache)
  132. (mc, _, task, fn) = bb.runqueue.split_tid_mcfn(tid)
  133. recipename = dataCache.pkg_fn[fn]
  134. self.lockedpnmap[fn] = recipename
  135. self.lockedhashfn[fn] = dataCache.hashfn[fn]
  136. unlocked = False
  137. if recipename in self.unlockedrecipes:
  138. unlocked = True
  139. else:
  140. def recipename_from_dep(dep):
  141. fn = bb.runqueue.fn_from_tid(dep)
  142. return dataCache.pkg_fn[fn]
  143. # If any unlocked recipe is in the direct dependencies then the
  144. # current recipe should be unlocked as well.
  145. depnames = [ recipename_from_dep(x) for x in deps if mc == bb.runqueue.mc_from_tid(x)]
  146. if any(x in y for y in depnames for x in self.unlockedrecipes):
  147. self.unlockedrecipes[recipename] = ''
  148. unlocked = True
  149. if not unlocked and recipename in self.lockedsigs:
  150. if task in self.lockedsigs[recipename]:
  151. h_locked = self.lockedsigs[recipename][task][0]
  152. var = self.lockedsigs[recipename][task][1]
  153. self.lockedhashes[tid] = h_locked
  154. unihash = super().get_unihash(tid)
  155. self.taskhash[tid] = h_locked
  156. #bb.warn("Using %s %s %s" % (recipename, task, h))
  157. if h != h_locked and h_locked != unihash:
  158. self.mismatch_msgs.append('The %s:%s sig is computed to be %s, but the sig is locked to %s in %s'
  159. % (recipename, task, h, h_locked, var))
  160. return h_locked
  161. self.lockedhashes[tid] = False
  162. #bb.warn("%s %s %s" % (recipename, task, h))
  163. return h
  164. def get_unihash(self, tid):
  165. if tid in self.lockedhashes and self.lockedhashes[tid]:
  166. return self.lockedhashes[tid]
  167. return super().get_unihash(tid)
  168. def dump_sigtask(self, fn, task, stampbase, runtime):
  169. tid = fn + ":" + task
  170. if tid in self.lockedhashes and self.lockedhashes[tid]:
  171. return
  172. super(bb.siggen.SignatureGeneratorBasicHash, self).dump_sigtask(fn, task, stampbase, runtime)
  173. def dump_lockedsigs(self, sigfile, taskfilter=None):
  174. types = {}
  175. for tid in self.runtaskdeps:
  176. if taskfilter:
  177. if not tid in taskfilter:
  178. continue
  179. fn = bb.runqueue.fn_from_tid(tid)
  180. t = self.lockedhashfn[fn].split(" ")[1].split(":")[5]
  181. t = 't-' + t.replace('_', '-')
  182. if t not in types:
  183. types[t] = []
  184. types[t].append(tid)
  185. with open(sigfile, "w") as f:
  186. l = sorted(types)
  187. for t in l:
  188. f.write('SIGGEN_LOCKEDSIGS_%s = "\\\n' % t)
  189. types[t].sort()
  190. sortedtid = sorted(types[t], key=lambda tid: self.lockedpnmap[bb.runqueue.fn_from_tid(tid)])
  191. for tid in sortedtid:
  192. (_, _, task, fn) = bb.runqueue.split_tid_mcfn(tid)
  193. if tid not in self.taskhash:
  194. continue
  195. f.write(" " + self.lockedpnmap[fn] + ":" + task + ":" + self.get_unihash(tid) + " \\\n")
  196. f.write(' "\n')
  197. f.write('SIGGEN_LOCKEDSIGS_TYPES_%s = "%s"' % (self.machine, " ".join(l)))
  198. def dump_siglist(self, sigfile):
  199. with open(sigfile, "w") as f:
  200. tasks = []
  201. for taskitem in self.taskhash:
  202. (fn, task) = taskitem.rsplit(":", 1)
  203. pn = self.lockedpnmap[fn]
  204. tasks.append((pn, task, fn, self.taskhash[taskitem]))
  205. for (pn, task, fn, taskhash) in sorted(tasks):
  206. f.write('%s:%s %s %s\n' % (pn, task, fn, taskhash))
  207. def checkhashes(self, sq_data, missed, found, d):
  208. warn_msgs = []
  209. error_msgs = []
  210. sstate_missing_msgs = []
  211. for tid in sq_data['hash']:
  212. if tid not in found:
  213. for pn in self.lockedsigs:
  214. taskname = bb.runqueue.taskname_from_tid(tid)
  215. if sq_data['hash'][tid] in iter(self.lockedsigs[pn].values()):
  216. if taskname == 'do_shared_workdir':
  217. continue
  218. sstate_missing_msgs.append("Locked sig is set for %s:%s (%s) yet not in sstate cache?"
  219. % (pn, taskname, sq_data['hash'][tid]))
  220. checklevel = d.getVar("SIGGEN_LOCKEDSIGS_TASKSIG_CHECK")
  221. if checklevel == 'warn':
  222. warn_msgs += self.mismatch_msgs
  223. elif checklevel == 'error':
  224. error_msgs += self.mismatch_msgs
  225. checklevel = d.getVar("SIGGEN_LOCKEDSIGS_SSTATE_EXISTS_CHECK")
  226. if checklevel == 'warn':
  227. warn_msgs += sstate_missing_msgs
  228. elif checklevel == 'error':
  229. error_msgs += sstate_missing_msgs
  230. if warn_msgs:
  231. bb.warn("\n".join(warn_msgs))
  232. if error_msgs:
  233. bb.fatal("\n".join(error_msgs))
  234. class SignatureGeneratorOEBasicHash(SignatureGeneratorOEBasicHashMixIn, bb.siggen.SignatureGeneratorBasicHash):
  235. name = "OEBasicHash"
  236. class SignatureGeneratorOEEquivHash(SignatureGeneratorOEBasicHashMixIn, bb.siggen.SignatureGeneratorUniHashMixIn, bb.siggen.SignatureGeneratorBasicHash):
  237. name = "OEEquivHash"
  238. def init_rundepcheck(self, data):
  239. super().init_rundepcheck(data)
  240. self.server = data.getVar('BB_HASHSERVE')
  241. if not self.server:
  242. bb.fatal("OEEquivHash requires BB_HASHSERVE to be set")
  243. self.method = data.getVar('SSTATE_HASHEQUIV_METHOD')
  244. if not self.method:
  245. bb.fatal("OEEquivHash requires SSTATE_HASHEQUIV_METHOD to be set")
  246. # Insert these classes into siggen's namespace so it can see and select them
  247. bb.siggen.SignatureGeneratorOEBasic = SignatureGeneratorOEBasic
  248. bb.siggen.SignatureGeneratorOEBasicHash = SignatureGeneratorOEBasicHash
  249. bb.siggen.SignatureGeneratorOEEquivHash = SignatureGeneratorOEEquivHash
  250. def find_siginfo(pn, taskname, taskhashlist, d):
  251. """ Find signature data files for comparison purposes """
  252. import fnmatch
  253. import glob
  254. if not taskname:
  255. # We have to derive pn and taskname
  256. key = pn
  257. splitit = key.split('.bb:')
  258. taskname = splitit[1]
  259. pn = os.path.basename(splitit[0]).split('_')[0]
  260. if key.startswith('virtual:native:'):
  261. pn = pn + '-native'
  262. hashfiles = {}
  263. filedates = {}
  264. def get_hashval(siginfo):
  265. if siginfo.endswith('.siginfo'):
  266. return siginfo.rpartition(':')[2].partition('_')[0]
  267. else:
  268. return siginfo.rpartition('.')[2]
  269. # First search in stamps dir
  270. localdata = d.createCopy()
  271. localdata.setVar('MULTIMACH_TARGET_SYS', '*')
  272. localdata.setVar('PN', pn)
  273. localdata.setVar('PV', '*')
  274. localdata.setVar('PR', '*')
  275. localdata.setVar('EXTENDPE', '')
  276. stamp = localdata.getVar('STAMP')
  277. if pn.startswith("gcc-source"):
  278. # gcc-source shared workdir is a special case :(
  279. stamp = localdata.expand("${STAMPS_DIR}/work-shared/gcc-${PV}-${PR}")
  280. filespec = '%s.%s.sigdata.*' % (stamp, taskname)
  281. foundall = False
  282. import glob
  283. for fullpath in glob.glob(filespec):
  284. match = False
  285. if taskhashlist:
  286. for taskhash in taskhashlist:
  287. if fullpath.endswith('.%s' % taskhash):
  288. hashfiles[taskhash] = fullpath
  289. if len(hashfiles) == len(taskhashlist):
  290. foundall = True
  291. break
  292. else:
  293. try:
  294. filedates[fullpath] = os.stat(fullpath).st_mtime
  295. except OSError:
  296. continue
  297. hashval = get_hashval(fullpath)
  298. hashfiles[hashval] = fullpath
  299. if not taskhashlist or (len(filedates) < 2 and not foundall):
  300. # That didn't work, look in sstate-cache
  301. hashes = taskhashlist or ['?' * 64]
  302. localdata = bb.data.createCopy(d)
  303. for hashval in hashes:
  304. localdata.setVar('PACKAGE_ARCH', '*')
  305. localdata.setVar('TARGET_VENDOR', '*')
  306. localdata.setVar('TARGET_OS', '*')
  307. localdata.setVar('PN', pn)
  308. localdata.setVar('PV', '*')
  309. localdata.setVar('PR', '*')
  310. localdata.setVar('BB_TASKHASH', hashval)
  311. swspec = localdata.getVar('SSTATE_SWSPEC')
  312. if taskname in ['do_fetch', 'do_unpack', 'do_patch', 'do_populate_lic', 'do_preconfigure'] and swspec:
  313. localdata.setVar('SSTATE_PKGSPEC', '${SSTATE_SWSPEC}')
  314. elif pn.endswith('-native') or "-cross-" in pn or "-crosssdk-" in pn:
  315. localdata.setVar('SSTATE_EXTRAPATH', "${NATIVELSBSTRING}/")
  316. sstatename = taskname[3:]
  317. filespec = '%s_%s.*.siginfo' % (localdata.getVar('SSTATE_PKG'), sstatename)
  318. matchedfiles = glob.glob(filespec)
  319. for fullpath in matchedfiles:
  320. actual_hashval = get_hashval(fullpath)
  321. if actual_hashval in hashfiles:
  322. continue
  323. hashfiles[hashval] = fullpath
  324. if not taskhashlist:
  325. try:
  326. filedates[fullpath] = os.stat(fullpath).st_mtime
  327. except:
  328. continue
  329. if taskhashlist:
  330. return hashfiles
  331. else:
  332. return filedates
  333. bb.siggen.find_siginfo = find_siginfo
  334. def sstate_get_manifest_filename(task, d):
  335. """
  336. Return the sstate manifest file path for a particular task.
  337. Also returns the datastore that can be used to query related variables.
  338. """
  339. d2 = d.createCopy()
  340. extrainf = d.getVarFlag("do_" + task, 'stamp-extra-info')
  341. if extrainf:
  342. d2.setVar("SSTATE_MANMACH", extrainf)
  343. return (d2.expand("${SSTATE_MANFILEPREFIX}.%s" % task), d2)
  344. def find_sstate_manifest(taskdata, taskdata2, taskname, d, multilibcache):
  345. d2 = d
  346. variant = ''
  347. curr_variant = ''
  348. if d.getVar("BBEXTENDCURR") == "multilib":
  349. curr_variant = d.getVar("BBEXTENDVARIANT")
  350. if "virtclass-multilib" not in d.getVar("OVERRIDES"):
  351. curr_variant = "invalid"
  352. if taskdata2.startswith("virtual:multilib"):
  353. variant = taskdata2.split(":")[2]
  354. if curr_variant != variant:
  355. if variant not in multilibcache:
  356. multilibcache[variant] = oe.utils.get_multilib_datastore(variant, d)
  357. d2 = multilibcache[variant]
  358. if taskdata.endswith("-native"):
  359. pkgarchs = ["${BUILD_ARCH}"]
  360. elif taskdata.startswith("nativesdk-"):
  361. pkgarchs = ["${SDK_ARCH}_${SDK_OS}", "allarch"]
  362. elif "-cross-canadian" in taskdata:
  363. pkgarchs = ["${SDK_ARCH}_${SDK_ARCH}-${SDKPKGSUFFIX}"]
  364. elif "-cross-" in taskdata:
  365. pkgarchs = ["${BUILD_ARCH}_${TARGET_ARCH}"]
  366. elif "-crosssdk" in taskdata:
  367. pkgarchs = ["${BUILD_ARCH}_${SDK_ARCH}_${SDK_OS}"]
  368. else:
  369. pkgarchs = ['${MACHINE_ARCH}']
  370. pkgarchs = pkgarchs + list(reversed(d2.getVar("PACKAGE_EXTRA_ARCHS").split()))
  371. pkgarchs.append('allarch')
  372. pkgarchs.append('${SDK_ARCH}_${SDK_ARCH}-${SDKPKGSUFFIX}')
  373. for pkgarch in pkgarchs:
  374. manifest = d2.expand("${SSTATE_MANIFESTS}/manifest-%s-%s.%s" % (pkgarch, taskdata, taskname))
  375. if os.path.exists(manifest):
  376. return manifest, d2
  377. bb.warn("Manifest %s not found in %s (variant '%s')?" % (manifest, d2.expand(" ".join(pkgarchs)), variant))
  378. return None, d2
  379. def OEOuthashBasic(path, sigfile, task, d):
  380. """
  381. Basic output hash function
  382. Calculates the output hash of a task by hashing all output file metadata,
  383. and file contents.
  384. """
  385. import hashlib
  386. import stat
  387. import pwd
  388. import grp
  389. def update_hash(s):
  390. s = s.encode('utf-8')
  391. h.update(s)
  392. if sigfile:
  393. sigfile.write(s)
  394. h = hashlib.sha256()
  395. prev_dir = os.getcwd()
  396. include_owners = os.environ.get('PSEUDO_DISABLED') == '0'
  397. try:
  398. os.chdir(path)
  399. update_hash("OEOuthashBasic\n")
  400. # It is only currently useful to get equivalent hashes for things that
  401. # can be restored from sstate. Since the sstate object is named using
  402. # SSTATE_PKGSPEC and the task name, those should be included in the
  403. # output hash calculation.
  404. update_hash("SSTATE_PKGSPEC=%s\n" % d.getVar('SSTATE_PKGSPEC'))
  405. update_hash("task=%s\n" % task)
  406. for root, dirs, files in os.walk('.', topdown=True):
  407. # Sort directories to ensure consistent ordering when recursing
  408. dirs.sort()
  409. files.sort()
  410. def process(path):
  411. s = os.lstat(path)
  412. if stat.S_ISDIR(s.st_mode):
  413. update_hash('d')
  414. elif stat.S_ISCHR(s.st_mode):
  415. update_hash('c')
  416. elif stat.S_ISBLK(s.st_mode):
  417. update_hash('b')
  418. elif stat.S_ISSOCK(s.st_mode):
  419. update_hash('s')
  420. elif stat.S_ISLNK(s.st_mode):
  421. update_hash('l')
  422. elif stat.S_ISFIFO(s.st_mode):
  423. update_hash('p')
  424. else:
  425. update_hash('-')
  426. def add_perm(mask, on, off='-'):
  427. if mask & s.st_mode:
  428. update_hash(on)
  429. else:
  430. update_hash(off)
  431. add_perm(stat.S_IRUSR, 'r')
  432. add_perm(stat.S_IWUSR, 'w')
  433. if stat.S_ISUID & s.st_mode:
  434. add_perm(stat.S_IXUSR, 's', 'S')
  435. else:
  436. add_perm(stat.S_IXUSR, 'x')
  437. add_perm(stat.S_IRGRP, 'r')
  438. add_perm(stat.S_IWGRP, 'w')
  439. if stat.S_ISGID & s.st_mode:
  440. add_perm(stat.S_IXGRP, 's', 'S')
  441. else:
  442. add_perm(stat.S_IXGRP, 'x')
  443. add_perm(stat.S_IROTH, 'r')
  444. add_perm(stat.S_IWOTH, 'w')
  445. if stat.S_ISVTX & s.st_mode:
  446. update_hash('t')
  447. else:
  448. add_perm(stat.S_IXOTH, 'x')
  449. if include_owners:
  450. update_hash(" %10s" % pwd.getpwuid(s.st_uid).pw_name)
  451. update_hash(" %10s" % grp.getgrgid(s.st_gid).gr_name)
  452. update_hash(" ")
  453. if stat.S_ISBLK(s.st_mode) or stat.S_ISCHR(s.st_mode):
  454. update_hash("%9s" % ("%d.%d" % (os.major(s.st_rdev), os.minor(s.st_rdev))))
  455. else:
  456. update_hash(" " * 9)
  457. update_hash(" ")
  458. if stat.S_ISREG(s.st_mode):
  459. update_hash("%10d" % s.st_size)
  460. else:
  461. update_hash(" " * 10)
  462. update_hash(" ")
  463. fh = hashlib.sha256()
  464. if stat.S_ISREG(s.st_mode):
  465. # Hash file contents
  466. with open(path, 'rb') as d:
  467. for chunk in iter(lambda: d.read(4096), b""):
  468. fh.update(chunk)
  469. update_hash(fh.hexdigest())
  470. else:
  471. update_hash(" " * len(fh.hexdigest()))
  472. update_hash(" %s" % path)
  473. if stat.S_ISLNK(s.st_mode):
  474. update_hash(" -> %s" % os.readlink(path))
  475. update_hash("\n")
  476. # Process this directory and all its child files
  477. process(root)
  478. for f in files:
  479. if f == 'fixmepath':
  480. continue
  481. process(os.path.join(root, f))
  482. finally:
  483. os.chdir(prev_dir)
  484. return h.hexdigest()