blob: f71190ad4b228b799821cbb3ed8d6b0b7b0be84d [file] [log] [blame]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001import hashlib
2import logging
3import os
4import re
5import tempfile
Patrick Williamsc0f7c042017-02-23 20:41:17 -06006import pickle
Patrick Williamsc124f4f2015-09-15 14:41:29 -05007import bb.data
Brad Bishop6e60e8b2018-02-01 10:27:11 -05008import difflib
9import simplediff
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050010from bb.checksum import FileChecksumCache
Patrick Williamsc124f4f2015-09-15 14:41:29 -050011
12logger = logging.getLogger('BitBake.SigGen')
13
Patrick Williamsc124f4f2015-09-15 14:41:29 -050014def init(d):
Patrick Williamsc0f7c042017-02-23 20:41:17 -060015 siggens = [obj for obj in globals().values()
Patrick Williamsc124f4f2015-09-15 14:41:29 -050016 if type(obj) is type and issubclass(obj, SignatureGenerator)]
17
Brad Bishop6e60e8b2018-02-01 10:27:11 -050018 desired = d.getVar("BB_SIGNATURE_HANDLER") or "noop"
Patrick Williamsc124f4f2015-09-15 14:41:29 -050019 for sg in siggens:
20 if desired == sg.name:
21 return sg(d)
22 break
23 else:
24 logger.error("Invalid signature generator '%s', using default 'noop'\n"
25 "Available generators: %s", desired,
26 ', '.join(obj.name for obj in siggens))
27 return SignatureGenerator(d)
28
29class SignatureGenerator(object):
30 """
31 """
32 name = "noop"
33
34 def __init__(self, data):
Brad Bishop37a0e4d2017-12-04 01:01:44 -050035 self.basehash = {}
Patrick Williamsc124f4f2015-09-15 14:41:29 -050036 self.taskhash = {}
37 self.runtaskdeps = {}
38 self.file_checksum_values = {}
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050039 self.taints = {}
Patrick Williamsc124f4f2015-09-15 14:41:29 -050040
41 def finalise(self, fn, d, varient):
42 return
43
44 def get_taskhash(self, fn, task, deps, dataCache):
45 return "0"
46
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050047 def writeout_file_checksum_cache(self):
48 """Write/update the file checksum cache onto disk"""
Patrick Williamsc124f4f2015-09-15 14:41:29 -050049 return
50
51 def stampfile(self, stampbase, file_name, taskname, extrainfo):
52 return ("%s.%s.%s" % (stampbase, taskname, extrainfo)).rstrip('.')
53
54 def stampcleanmask(self, stampbase, file_name, taskname, extrainfo):
55 return ("%s.%s.%s" % (stampbase, taskname, extrainfo)).rstrip('.')
56
57 def dump_sigtask(self, fn, task, stampbase, runtime):
58 return
59
60 def invalidate_task(self, task, d, fn):
61 bb.build.del_stamp(task, d, fn)
62
63 def dump_sigs(self, dataCache, options):
64 return
65
66 def get_taskdata(self):
Brad Bishop37a0e4d2017-12-04 01:01:44 -050067 return (self.runtaskdeps, self.taskhash, self.file_checksum_values, self.taints, self.basehash)
Patrick Williamsc124f4f2015-09-15 14:41:29 -050068
69 def set_taskdata(self, data):
Brad Bishop37a0e4d2017-12-04 01:01:44 -050070 self.runtaskdeps, self.taskhash, self.file_checksum_values, self.taints, self.basehash = data
Patrick Williamsc124f4f2015-09-15 14:41:29 -050071
72class SignatureGeneratorBasic(SignatureGenerator):
73 """
74 """
75 name = "basic"
76
77 def __init__(self, data):
78 self.basehash = {}
79 self.taskhash = {}
80 self.taskdeps = {}
81 self.runtaskdeps = {}
82 self.file_checksum_values = {}
Patrick Williamsf1e5d692016-03-30 15:21:19 -050083 self.taints = {}
Patrick Williamsc124f4f2015-09-15 14:41:29 -050084 self.gendeps = {}
85 self.lookupcache = {}
86 self.pkgnameextract = re.compile("(?P<fn>.*)\..*")
Brad Bishop6e60e8b2018-02-01 10:27:11 -050087 self.basewhitelist = set((data.getVar("BB_HASHBASE_WHITELIST") or "").split())
Patrick Williamsc124f4f2015-09-15 14:41:29 -050088 self.taskwhitelist = None
89 self.init_rundepcheck(data)
Brad Bishop6e60e8b2018-02-01 10:27:11 -050090 checksum_cache_file = data.getVar("BB_HASH_CHECKSUM_CACHE_FILE")
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050091 if checksum_cache_file:
92 self.checksum_cache = FileChecksumCache()
93 self.checksum_cache.init_cache(data, checksum_cache_file)
94 else:
95 self.checksum_cache = None
Patrick Williamsc124f4f2015-09-15 14:41:29 -050096
97 def init_rundepcheck(self, data):
Brad Bishop6e60e8b2018-02-01 10:27:11 -050098 self.taskwhitelist = data.getVar("BB_HASHTASK_WHITELIST") or None
Patrick Williamsc124f4f2015-09-15 14:41:29 -050099 if self.taskwhitelist:
100 self.twl = re.compile(self.taskwhitelist)
101 else:
102 self.twl = None
103
104 def _build_data(self, fn, d):
105
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500106 ignore_mismatch = ((d.getVar("BB_HASH_IGNORE_MISMATCH") or '') == '1')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500107 tasklist, gendeps, lookupcache = bb.data.generate_dependencies(d)
108
109 taskdeps = {}
110 basehash = {}
111
112 for task in tasklist:
113 data = lookupcache[task]
114
115 if data is None:
116 bb.error("Task %s from %s seems to be empty?!" % (task, fn))
117 data = ''
118
119 gendeps[task] -= self.basewhitelist
120 newdeps = gendeps[task]
121 seen = set()
122 while newdeps:
123 nextdeps = newdeps
124 seen |= nextdeps
125 newdeps = set()
126 for dep in nextdeps:
127 if dep in self.basewhitelist:
128 continue
129 gendeps[dep] -= self.basewhitelist
130 newdeps |= gendeps[dep]
131 newdeps -= seen
132
133 alldeps = sorted(seen)
134 for dep in alldeps:
135 data = data + dep
136 var = lookupcache[dep]
137 if var is not None:
138 data = data + str(var)
Brad Bishop37a0e4d2017-12-04 01:01:44 -0500139 datahash = hashlib.md5(data.encode("utf-8")).hexdigest()
140 k = fn + "." + task
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500141 if not ignore_mismatch and k in self.basehash and self.basehash[k] != datahash:
Brad Bishop37a0e4d2017-12-04 01:01:44 -0500142 bb.error("When reparsing %s, the basehash value changed from %s to %s. The metadata is not deterministic and this needs to be fixed." % (k, self.basehash[k], datahash))
143 self.basehash[k] = datahash
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500144 taskdeps[task] = alldeps
145
146 self.taskdeps[fn] = taskdeps
147 self.gendeps[fn] = gendeps
148 self.lookupcache[fn] = lookupcache
149
150 return taskdeps
151
152 def finalise(self, fn, d, variant):
153
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600154 mc = d.getVar("__BBMULTICONFIG", False) or ""
155 if variant or mc:
156 fn = bb.cache.realfn2virtual(fn, variant, mc)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500157
158 try:
159 taskdeps = self._build_data(fn, d)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500160 except bb.parse.SkipRecipe:
161 raise
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500162 except:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500163 bb.warn("Error during finalise of %s" % fn)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500164 raise
165
166 #Slow but can be useful for debugging mismatched basehashes
167 #for task in self.taskdeps[fn]:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500168 # self.dump_sigtask(fn, task, d.getVar("STAMP"), False)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500169
170 for task in taskdeps:
171 d.setVar("BB_BASEHASH_task-%s" % task, self.basehash[fn + "." + task])
172
173 def rundep_check(self, fn, recipename, task, dep, depname, dataCache):
174 # Return True if we should keep the dependency, False to drop it
175 # We only manipulate the dependencies for packages not in the whitelist
176 if self.twl and not self.twl.search(recipename):
177 # then process the actual dependencies
178 if self.twl.search(depname):
179 return False
180 return True
181
182 def read_taint(self, fn, task, stampbase):
183 taint = None
184 try:
185 with open(stampbase + '.' + task + '.taint', 'r') as taintf:
186 taint = taintf.read()
187 except IOError:
188 pass
189 return taint
190
191 def get_taskhash(self, fn, task, deps, dataCache):
192 k = fn + "." + task
193 data = dataCache.basetaskhash[k]
Brad Bishop37a0e4d2017-12-04 01:01:44 -0500194 self.basehash[k] = data
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500195 self.runtaskdeps[k] = []
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500196 self.file_checksum_values[k] = []
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500197 recipename = dataCache.pkg_fn[fn]
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500198
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500199 for dep in sorted(deps, key=clean_basepath):
200 depname = dataCache.pkg_fn[self.pkgnameextract.search(dep).group('fn')]
201 if not self.rundep_check(fn, recipename, task, dep, depname, dataCache):
202 continue
203 if dep not in self.taskhash:
204 bb.fatal("%s is not in taskhash, caller isn't calling in dependency order?", dep)
205 data = data + self.taskhash[dep]
206 self.runtaskdeps[k].append(dep)
207
208 if task in dataCache.file_checksums[fn]:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500209 if self.checksum_cache:
210 checksums = self.checksum_cache.get_checksums(dataCache.file_checksums[fn][task], recipename)
211 else:
212 checksums = bb.fetch2.get_file_checksums(dataCache.file_checksums[fn][task], recipename)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500213 for (f,cs) in checksums:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500214 self.file_checksum_values[k].append((f,cs))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500215 if cs:
216 data = data + cs
217
218 taskdep = dataCache.task_deps[fn]
219 if 'nostamp' in taskdep and task in taskdep['nostamp']:
220 # Nostamp tasks need an implicit taint so that they force any dependent tasks to run
221 import uuid
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500222 taint = str(uuid.uuid4())
223 data = data + taint
224 self.taints[k] = "nostamp:" + taint
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500225
226 taint = self.read_taint(fn, task, dataCache.stamp[fn])
227 if taint:
228 data = data + taint
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500229 self.taints[k] = taint
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600230 logger.warning("%s is tainted from a forced run" % k)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500231
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600232 h = hashlib.md5(data.encode("utf-8")).hexdigest()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500233 self.taskhash[k] = h
234 #d.setVar("BB_TASKHASH_task-%s" % task, taskhash[task])
235 return h
236
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500237 def writeout_file_checksum_cache(self):
238 """Write/update the file checksum cache onto disk"""
239 if self.checksum_cache:
240 self.checksum_cache.save_extras()
241 self.checksum_cache.save_merge()
242 else:
243 bb.fetch2.fetcher_parse_save()
244 bb.fetch2.fetcher_parse_done()
245
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500246 def dump_sigtask(self, fn, task, stampbase, runtime):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500247
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500248 k = fn + "." + task
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500249 referencestamp = stampbase
250 if isinstance(runtime, str) and runtime.startswith("customfile"):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500251 sigfile = stampbase
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500252 referencestamp = runtime[11:]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500253 elif runtime and k in self.taskhash:
254 sigfile = stampbase + "." + task + ".sigdata" + "." + self.taskhash[k]
255 else:
256 sigfile = stampbase + "." + task + ".sigbasedata" + "." + self.basehash[k]
257
258 bb.utils.mkdirhier(os.path.dirname(sigfile))
259
260 data = {}
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500261 data['task'] = task
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500262 data['basewhitelist'] = self.basewhitelist
263 data['taskwhitelist'] = self.taskwhitelist
264 data['taskdeps'] = self.taskdeps[fn][task]
265 data['basehash'] = self.basehash[k]
266 data['gendeps'] = {}
267 data['varvals'] = {}
268 data['varvals'][task] = self.lookupcache[fn][task]
269 for dep in self.taskdeps[fn][task]:
270 if dep in self.basewhitelist:
271 continue
272 data['gendeps'][dep] = self.gendeps[fn][dep]
273 data['varvals'][dep] = self.lookupcache[fn][dep]
274
275 if runtime and k in self.taskhash:
276 data['runtaskdeps'] = self.runtaskdeps[k]
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500277 data['file_checksum_values'] = [(os.path.basename(f), cs) for f,cs in self.file_checksum_values[k]]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500278 data['runtaskhashes'] = {}
279 for dep in data['runtaskdeps']:
280 data['runtaskhashes'][dep] = self.taskhash[dep]
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500281 data['taskhash'] = self.taskhash[k]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500282
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500283 taint = self.read_taint(fn, task, referencestamp)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500284 if taint:
285 data['taint'] = taint
286
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500287 if runtime and k in self.taints:
288 if 'nostamp:' in self.taints[k]:
289 data['taint'] = self.taints[k]
290
Brad Bishop37a0e4d2017-12-04 01:01:44 -0500291 computed_basehash = calc_basehash(data)
292 if computed_basehash != self.basehash[k]:
293 bb.error("Basehash mismatch %s versus %s for %s" % (computed_basehash, self.basehash[k], k))
294 if runtime and k in self.taskhash:
295 computed_taskhash = calc_taskhash(data)
296 if computed_taskhash != self.taskhash[k]:
297 bb.error("Taskhash mismatch %s versus %s for %s" % (computed_taskhash, self.taskhash[k], k))
298 sigfile = sigfile.replace(self.taskhash[k], computed_taskhash)
299
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500300 fd, tmpfile = tempfile.mkstemp(dir=os.path.dirname(sigfile), prefix="sigtask.")
301 try:
302 with os.fdopen(fd, "wb") as stream:
303 p = pickle.dump(data, stream, -1)
304 stream.flush()
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600305 os.chmod(tmpfile, 0o664)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500306 os.rename(tmpfile, sigfile)
307 except (OSError, IOError) as err:
308 try:
309 os.unlink(tmpfile)
310 except OSError:
311 pass
312 raise err
313
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500314 def dump_sigfn(self, fn, dataCaches, options):
315 if fn in self.taskdeps:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500316 for task in self.taskdeps[fn]:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600317 tid = fn + ":" + task
318 (mc, _, _) = bb.runqueue.split_tid(tid)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500319 k = fn + "." + task
320 if k not in self.taskhash:
321 continue
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600322 if dataCaches[mc].basetaskhash[k] != self.basehash[k]:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500323 bb.error("Bitbake's cached basehash does not match the one we just generated (%s)!" % k)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600324 bb.error("The mismatched hashes were %s and %s" % (dataCaches[mc].basetaskhash[k], self.basehash[k]))
325 self.dump_sigtask(fn, task, dataCaches[mc].stamp[fn], True)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500326
327class SignatureGeneratorBasicHash(SignatureGeneratorBasic):
328 name = "basichash"
329
330 def stampfile(self, stampbase, fn, taskname, extrainfo, clean=False):
331 if taskname != "do_setscene" and taskname.endswith("_setscene"):
332 k = fn + "." + taskname[:-9]
333 else:
334 k = fn + "." + taskname
335 if clean:
336 h = "*"
337 elif k in self.taskhash:
338 h = self.taskhash[k]
339 else:
340 # If k is not in basehash, then error
341 h = self.basehash[k]
342 return ("%s.%s.%s.%s" % (stampbase, taskname, h, extrainfo)).rstrip('.')
343
344 def stampcleanmask(self, stampbase, fn, taskname, extrainfo):
345 return self.stampfile(stampbase, fn, taskname, extrainfo, clean=True)
346
347 def invalidate_task(self, task, d, fn):
348 bb.note("Tainting hash to force rebuild of task %s, %s" % (fn, task))
349 bb.build.write_taint(task, d, fn)
350
351def dump_this_task(outfile, d):
352 import bb.parse
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500353 fn = d.getVar("BB_FILENAME")
354 task = "do_" + d.getVar("BB_CURRENTTASK")
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500355 referencestamp = bb.build.stamp_internal(task, d, None, True)
356 bb.parse.siggen.dump_sigtask(fn, task, outfile, "customfile:" + referencestamp)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500357
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500358def init_colors(enable_color):
359 """Initialise colour dict for passing to compare_sigfiles()"""
360 # First set up the colours
361 colors = {'color_title': '\033[1;37;40m',
362 'color_default': '\033[0;37;40m',
363 'color_add': '\033[1;32;40m',
364 'color_remove': '\033[1;31;40m',
365 }
366 # Leave all keys present but clear the values
367 if not enable_color:
368 for k in colors.keys():
369 colors[k] = ''
370 return colors
371
372def worddiff_str(oldstr, newstr, colors=None):
373 if not colors:
374 colors = init_colors(False)
375 diff = simplediff.diff(oldstr.split(' '), newstr.split(' '))
376 ret = []
377 for change, value in diff:
378 value = ' '.join(value)
379 if change == '=':
380 ret.append(value)
381 elif change == '+':
382 item = '{color_add}{{+{value}+}}{color_default}'.format(value=value, **colors)
383 ret.append(item)
384 elif change == '-':
385 item = '{color_remove}[-{value}-]{color_default}'.format(value=value, **colors)
386 ret.append(item)
387 whitespace_note = ''
388 if oldstr != newstr and ' '.join(oldstr.split()) == ' '.join(newstr.split()):
389 whitespace_note = ' (whitespace changed)'
390 return '"%s"%s' % (' '.join(ret), whitespace_note)
391
392def list_inline_diff(oldlist, newlist, colors=None):
393 if not colors:
394 colors = init_colors(False)
395 diff = simplediff.diff(oldlist, newlist)
396 ret = []
397 for change, value in diff:
398 value = ' '.join(value)
399 if change == '=':
400 ret.append("'%s'" % value)
401 elif change == '+':
402 item = '{color_add}+{value}{color_default}'.format(value=value, **colors)
403 ret.append(item)
404 elif change == '-':
405 item = '{color_remove}-{value}{color_default}'.format(value=value, **colors)
406 ret.append(item)
407 return '[%s]' % (', '.join(ret))
408
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500409def clean_basepath(a):
Brad Bishop37a0e4d2017-12-04 01:01:44 -0500410 mc = None
411 if a.startswith("multiconfig:"):
412 _, mc, a = a.split(":", 2)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500413 b = a.rsplit("/", 2)[1] + '/' + a.rsplit("/", 2)[2]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500414 if a.startswith("virtual:"):
415 b = b + ":" + a.rsplit(":", 1)[0]
Brad Bishop37a0e4d2017-12-04 01:01:44 -0500416 if mc:
417 b = b + ":multiconfig:" + mc
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500418 return b
419
420def clean_basepaths(a):
421 b = {}
422 for x in a:
423 b[clean_basepath(x)] = a[x]
424 return b
425
426def clean_basepaths_list(a):
427 b = []
428 for x in a:
429 b.append(clean_basepath(x))
430 return b
431
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500432def compare_sigfiles(a, b, recursecb=None, color=False, collapsed=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500433 output = []
434
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500435 colors = init_colors(color)
436 def color_format(formatstr, **values):
437 """
438 Return colour formatted string.
439 NOTE: call with the format string, not an already formatted string
440 containing values (otherwise you could have trouble with { and }
441 characters)
442 """
443 if not formatstr.endswith('{color_default}'):
444 formatstr += '{color_default}'
445 # In newer python 3 versions you can pass both of these directly,
446 # but we only require 3.4 at the moment
447 formatparams = {}
448 formatparams.update(colors)
449 formatparams.update(values)
450 return formatstr.format(**formatparams)
451
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600452 with open(a, 'rb') as f:
453 p1 = pickle.Unpickler(f)
454 a_data = p1.load()
455 with open(b, 'rb') as f:
456 p2 = pickle.Unpickler(f)
457 b_data = p2.load()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500458
459 def dict_diff(a, b, whitelist=set()):
460 sa = set(a.keys())
461 sb = set(b.keys())
462 common = sa & sb
463 changed = set()
464 for i in common:
465 if a[i] != b[i] and i not in whitelist:
466 changed.add(i)
467 added = sb - sa
468 removed = sa - sb
469 return changed, added, removed
470
471 def file_checksums_diff(a, b):
472 from collections import Counter
473 # Handle old siginfo format
474 if isinstance(a, dict):
475 a = [(os.path.basename(f), cs) for f, cs in a.items()]
476 if isinstance(b, dict):
477 b = [(os.path.basename(f), cs) for f, cs in b.items()]
478 # Compare lists, ensuring we can handle duplicate filenames if they exist
479 removedcount = Counter(a)
480 removedcount.subtract(b)
481 addedcount = Counter(b)
482 addedcount.subtract(a)
483 added = []
484 for x in b:
485 if addedcount[x] > 0:
486 addedcount[x] -= 1
487 added.append(x)
488 removed = []
489 changed = []
490 for x in a:
491 if removedcount[x] > 0:
492 removedcount[x] -= 1
493 for y in added:
494 if y[0] == x[0]:
495 changed.append((x[0], x[1], y[1]))
496 added.remove(y)
497 break
498 else:
499 removed.append(x)
500 added = [x[0] for x in added]
501 removed = [x[0] for x in removed]
502 return changed, added, removed
503
504 if 'basewhitelist' in a_data and a_data['basewhitelist'] != b_data['basewhitelist']:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500505 output.append(color_format("{color_title}basewhitelist changed{color_default} from '%s' to '%s'") % (a_data['basewhitelist'], b_data['basewhitelist']))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500506 if a_data['basewhitelist'] and b_data['basewhitelist']:
507 output.append("changed items: %s" % a_data['basewhitelist'].symmetric_difference(b_data['basewhitelist']))
508
509 if 'taskwhitelist' in a_data and a_data['taskwhitelist'] != b_data['taskwhitelist']:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500510 output.append(color_format("{color_title}taskwhitelist changed{color_default} from '%s' to '%s'") % (a_data['taskwhitelist'], b_data['taskwhitelist']))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500511 if a_data['taskwhitelist'] and b_data['taskwhitelist']:
512 output.append("changed items: %s" % a_data['taskwhitelist'].symmetric_difference(b_data['taskwhitelist']))
513
514 if a_data['taskdeps'] != b_data['taskdeps']:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500515 output.append(color_format("{color_title}Task dependencies changed{color_default} from:\n%s\nto:\n%s") % (sorted(a_data['taskdeps']), sorted(b_data['taskdeps'])))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500516
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500517 if a_data['basehash'] != b_data['basehash'] and not collapsed:
518 output.append(color_format("{color_title}basehash changed{color_default} from %s to %s") % (a_data['basehash'], b_data['basehash']))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500519
520 changed, added, removed = dict_diff(a_data['gendeps'], b_data['gendeps'], a_data['basewhitelist'] & b_data['basewhitelist'])
521 if changed:
522 for dep in changed:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500523 output.append(color_format("{color_title}List of dependencies for variable %s changed from '{color_default}%s{color_title}' to '{color_default}%s{color_title}'") % (dep, a_data['gendeps'][dep], b_data['gendeps'][dep]))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500524 if a_data['gendeps'][dep] and b_data['gendeps'][dep]:
525 output.append("changed items: %s" % a_data['gendeps'][dep].symmetric_difference(b_data['gendeps'][dep]))
526 if added:
527 for dep in added:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500528 output.append(color_format("{color_title}Dependency on variable %s was added") % (dep))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500529 if removed:
530 for dep in removed:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500531 output.append(color_format("{color_title}Dependency on Variable %s was removed") % (dep))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500532
533
534 changed, added, removed = dict_diff(a_data['varvals'], b_data['varvals'])
535 if changed:
536 for dep in changed:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500537 oldval = a_data['varvals'][dep]
538 newval = b_data['varvals'][dep]
539 if newval and oldval and ('\n' in oldval or '\n' in newval):
540 diff = difflib.unified_diff(oldval.splitlines(), newval.splitlines(), lineterm='')
541 # Cut off the first two lines, since we aren't interested in
542 # the old/new filename (they are blank anyway in this case)
543 difflines = list(diff)[2:]
544 if color:
545 # Add colour to diff output
546 for i, line in enumerate(difflines):
547 if line.startswith('+'):
548 line = color_format('{color_add}{line}', line=line)
549 difflines[i] = line
550 elif line.startswith('-'):
551 line = color_format('{color_remove}{line}', line=line)
552 difflines[i] = line
553 output.append(color_format("{color_title}Variable {var} value changed:{color_default}\n{diff}", var=dep, diff='\n'.join(difflines)))
554 elif newval and oldval and (' ' in oldval or ' ' in newval):
555 output.append(color_format("{color_title}Variable {var} value changed:{color_default}\n{diff}", var=dep, diff=worddiff_str(oldval, newval, colors)))
556 else:
557 output.append(color_format("{color_title}Variable {var} value changed from '{color_default}{oldval}{color_title}' to '{color_default}{newval}{color_title}'{color_default}", var=dep, oldval=oldval, newval=newval))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500558
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600559 if not 'file_checksum_values' in a_data:
560 a_data['file_checksum_values'] = {}
561 if not 'file_checksum_values' in b_data:
562 b_data['file_checksum_values'] = {}
563
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500564 changed, added, removed = file_checksums_diff(a_data['file_checksum_values'], b_data['file_checksum_values'])
565 if changed:
566 for f, old, new in changed:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500567 output.append(color_format("{color_title}Checksum for file %s changed{color_default} from %s to %s") % (f, old, new))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500568 if added:
569 for f in added:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500570 output.append(color_format("{color_title}Dependency on checksum of file %s was added") % (f))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500571 if removed:
572 for f in removed:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500573 output.append(color_format("{color_title}Dependency on checksum of file %s was removed") % (f))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500574
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600575 if not 'runtaskdeps' in a_data:
576 a_data['runtaskdeps'] = {}
577 if not 'runtaskdeps' in b_data:
578 b_data['runtaskdeps'] = {}
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500579
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500580 if not collapsed:
581 if len(a_data['runtaskdeps']) != len(b_data['runtaskdeps']):
582 changed = ["Number of task dependencies changed"]
583 else:
584 changed = []
585 for idx, task in enumerate(a_data['runtaskdeps']):
586 a = a_data['runtaskdeps'][idx]
587 b = b_data['runtaskdeps'][idx]
588 if a_data['runtaskhashes'][a] != b_data['runtaskhashes'][b] and not collapsed:
589 changed.append("%s with hash %s\n changed to\n%s with hash %s" % (clean_basepath(a), a_data['runtaskhashes'][a], clean_basepath(b), b_data['runtaskhashes'][b]))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500590
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500591 if changed:
592 clean_a = clean_basepaths_list(a_data['runtaskdeps'])
593 clean_b = clean_basepaths_list(b_data['runtaskdeps'])
594 if clean_a != clean_b:
595 output.append(color_format("{color_title}runtaskdeps changed:{color_default}\n%s") % list_inline_diff(clean_a, clean_b, colors))
596 else:
597 output.append(color_format("{color_title}runtaskdeps changed:"))
598 output.append("\n".join(changed))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500599
600
601 if 'runtaskhashes' in a_data and 'runtaskhashes' in b_data:
602 a = a_data['runtaskhashes']
603 b = b_data['runtaskhashes']
604 changed, added, removed = dict_diff(a, b)
605 if added:
606 for dep in added:
607 bdep_found = False
608 if removed:
609 for bdep in removed:
610 if b[dep] == a[bdep]:
611 #output.append("Dependency on task %s was replaced by %s with same hash" % (dep, bdep))
612 bdep_found = True
613 if not bdep_found:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500614 output.append(color_format("{color_title}Dependency on task %s was added{color_default} with hash %s") % (clean_basepath(dep), b[dep]))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500615 if removed:
616 for dep in removed:
617 adep_found = False
618 if added:
619 for adep in added:
620 if b[adep] == a[dep]:
621 #output.append("Dependency on task %s was replaced by %s with same hash" % (adep, dep))
622 adep_found = True
623 if not adep_found:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500624 output.append(color_format("{color_title}Dependency on task %s was removed{color_default} with hash %s") % (clean_basepath(dep), a[dep]))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500625 if changed:
626 for dep in changed:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500627 if not collapsed:
628 output.append(color_format("{color_title}Hash for dependent task %s changed{color_default} from %s to %s") % (clean_basepath(dep), a[dep], b[dep]))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500629 if callable(recursecb):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500630 recout = recursecb(dep, a[dep], b[dep])
631 if recout:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500632 if collapsed:
633 output.extend(recout)
634 else:
635 # If a dependent hash changed, might as well print the line above and then defer to the changes in
636 # that hash since in all likelyhood, they're the same changes this task also saw.
637 output = [output[-1]] + recout
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500638
639 a_taint = a_data.get('taint', None)
640 b_taint = b_data.get('taint', None)
641 if a_taint != b_taint:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500642 output.append(color_format("{color_title}Taint (by forced/invalidated task) changed{color_default} from %s to %s") % (a_taint, b_taint))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500643
644 return output
645
646
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500647def calc_basehash(sigdata):
648 task = sigdata['task']
649 basedata = sigdata['varvals'][task]
650
651 if basedata is None:
652 basedata = ''
653
654 alldeps = sigdata['taskdeps']
655 for dep in alldeps:
656 basedata = basedata + dep
657 val = sigdata['varvals'][dep]
658 if val is not None:
659 basedata = basedata + str(val)
660
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600661 return hashlib.md5(basedata.encode("utf-8")).hexdigest()
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500662
663def calc_taskhash(sigdata):
664 data = sigdata['basehash']
665
666 for dep in sigdata['runtaskdeps']:
667 data = data + sigdata['runtaskhashes'][dep]
668
669 for c in sigdata['file_checksum_values']:
Brad Bishop37a0e4d2017-12-04 01:01:44 -0500670 if c[1]:
671 data = data + c[1]
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500672
673 if 'taint' in sigdata:
674 if 'nostamp:' in sigdata['taint']:
675 data = data + sigdata['taint'][8:]
676 else:
677 data = data + sigdata['taint']
678
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600679 return hashlib.md5(data.encode("utf-8")).hexdigest()
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500680
681
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500682def dump_sigfile(a):
683 output = []
684
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600685 with open(a, 'rb') as f:
686 p1 = pickle.Unpickler(f)
687 a_data = p1.load()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500688
689 output.append("basewhitelist: %s" % (a_data['basewhitelist']))
690
691 output.append("taskwhitelist: %s" % (a_data['taskwhitelist']))
692
693 output.append("Task dependencies: %s" % (sorted(a_data['taskdeps'])))
694
695 output.append("basehash: %s" % (a_data['basehash']))
696
697 for dep in a_data['gendeps']:
698 output.append("List of dependencies for variable %s is %s" % (dep, a_data['gendeps'][dep]))
699
700 for dep in a_data['varvals']:
701 output.append("Variable %s value is %s" % (dep, a_data['varvals'][dep]))
702
703 if 'runtaskdeps' in a_data:
704 output.append("Tasks this task depends on: %s" % (a_data['runtaskdeps']))
705
706 if 'file_checksum_values' in a_data:
707 output.append("This task depends on the checksums of files: %s" % (a_data['file_checksum_values']))
708
709 if 'runtaskhashes' in a_data:
710 for dep in a_data['runtaskhashes']:
711 output.append("Hash for dependent task %s is %s" % (dep, a_data['runtaskhashes'][dep]))
712
713 if 'taint' in a_data:
714 output.append("Tainted (by forced/invalidated task): %s" % a_data['taint'])
715
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500716 if 'task' in a_data:
717 computed_basehash = calc_basehash(a_data)
718 output.append("Computed base hash is %s and from file %s" % (computed_basehash, a_data['basehash']))
719 else:
720 output.append("Unable to compute base hash")
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500721
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500722 computed_taskhash = calc_taskhash(a_data)
723 output.append("Computed task hash is %s" % computed_taskhash)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500724
725 return output