comparison cutils/shasum.py @ 83:05e2bf4796fd

Add an option "--checklist-allow-distinfo" to allow FreeBSD "distinfo" formatted files as checkfiles. This effectively ignores SIZE() and TIMESTAMP = like lines instead of throwing errors. This allows to use a package's distinfo file directoy as checkfile.
author Franz Glasner <f.glasner@feldmann-mg.com>
date Thu, 10 Mar 2022 16:17:53 +0100
parents 8708c34e2723
children 163de6dd6e05
comparison
equal deleted inserted replaced
82:7ddc249404d5 83:05e2bf4796fd
77 aparser.add_argument( 77 aparser.add_argument(
78 "--checklist", "-C", metavar="CHECKLIST", 78 "--checklist", "-C", metavar="CHECKLIST",
79 help="""Compare the checksum of each FILE against the checksums in 79 help="""Compare the checksum of each FILE against the checksums in
80 the CHECKLIST. Any specified FILE that is not listed in the CHECKLIST will 80 the CHECKLIST. Any specified FILE that is not listed in the CHECKLIST will
81 generate an error.""") 81 generate an error.""")
82 aparser.add_argument(
83 "--checklist-allow-distinfo", action="store_true",
84 dest="allow_distinfo",
85 help='Allow FreeBSD "distinfo" formatted checklists: ignore SIZE and TIMESTAMP lines.')
82 86
83 aparser.add_argument( 87 aparser.add_argument(
84 "--reverse", "-r", action="store_false", dest="bsd", default=False, 88 "--reverse", "-r", action="store_false", dest="bsd", default=False,
85 help="Explicitely select normal coreutils style output (to be option compatible with BSD style commands and :command:`openssl dgst -r`)") 89 help="Explicitely select normal coreutils style output (to be option compatible with BSD style commands and :command:`openssl dgst -r`)")
86 aparser.add_argument( 90 aparser.add_argument(
258 if not opts.files or (len(opts.files) == 1 and opts.files[0] == '-'): 262 if not opts.files or (len(opts.files) == 1 and opts.files[0] == '-'):
259 for checkline in sys.stdin: 263 for checkline in sys.stdin:
260 if not checkline: 264 if not checkline:
261 continue 265 continue
262 r, fn, tag = handle_checkline(opts, checkline) 266 r, fn, tag = handle_checkline(opts, checkline)
267 if tag in ("SIZE", "TIMESTAMP"):
268 assert opts.allow_distinfo
269 continue
263 print("{}: {}: {}".format(tag, fn, r.upper()), file=dest) 270 print("{}: {}: {}".format(tag, fn, r.upper()), file=dest)
264 if r != "ok" and exit_code == 0: 271 if r != "ok" and exit_code == 0:
265 exit_code = 1 272 exit_code = 1
266 else: 273 else:
267 for fn in opts.files: 274 for fn in opts.files:
268 with io.open(fn, "rt", encoding="utf-8") as checkfile: 275 with io.open(fn, "rt", encoding="utf-8") as checkfile:
269 for checkline in checkfile: 276 for checkline in checkfile:
270 if not checkline: 277 if not checkline:
271 continue 278 continue
272 r, fn, tag = handle_checkline(opts, checkline) 279 r, fn, tag = handle_checkline(opts, checkline)
280 if tag in ("SIZE", "TIMESTAMP"):
281 assert opts.allow_distinfo
282 continue
273 print("{}: {}: {}".format(tag, fn, r.upper()), file=dest) 283 print("{}: {}: {}".format(tag, fn, r.upper()), file=dest)
274 if r != "ok" and exit_code == 0: 284 if r != "ok" and exit_code == 0:
275 exit_code = 1 285 exit_code = 1
276 return exit_code 286 return exit_code
277 287
286 parts = parse_digest_line(opts, line) 296 parts = parse_digest_line(opts, line)
287 if not parts: 297 if not parts:
288 raise ValueError( 298 raise ValueError(
289 "improperly formatted digest line: {}".format(line)) 299 "improperly formatted digest line: {}".format(line))
290 tag, algo, fn, digest = parts 300 tag, algo, fn, digest = parts
301 if tag in ("SIZE", "TIMESTAMP"):
302 assert opts.allow_distinfo
303 return (None, None, tag)
291 try: 304 try:
292 d = compute_digest_file(algo, fn) 305 d = compute_digest_file(algo, fn)
293 if compare_digests_equal(d, digest, algo): 306 if compare_digests_equal(d, digest, algo):
294 return ("ok", fn, tag) 307 return ("ok", fn, tag)
295 else: 308 else:
310 continue 323 continue
311 parts = parse_digest_line(opts, checkline) 324 parts = parse_digest_line(opts, checkline)
312 if not parts: 325 if not parts:
313 raise ValueError( 326 raise ValueError(
314 "improperly formatted digest line: {}".format(checkline)) 327 "improperly formatted digest line: {}".format(checkline))
328 if parts[0] in ("SIZE", "TIMESTAMP"):
329 assert opts.allow_distinfo
330 continue
315 fn = normalize_filename(parts[2], strip_leading_dot_slash=True) 331 fn = normalize_filename(parts[2], strip_leading_dot_slash=True)
316 if fn in filenames: 332 if fn in filenames:
317 return parts 333 return parts
318 else: 334 else:
319 return None 335 return None
320 336
321 337
322 def parse_digest_line(opts, line): 338 def parse_digest_line(opts, line):
323 """Parse a `line` of a digest file and return its parts. 339 """Parse a `line` of a digest file and return its parts.
340
341 This is rather strict. But if `opts.allow_distinfo` is `True` then
342 some additional keywords ``SIZE`` and ``TIMESTAMP``are recignized
343 and returned. The caller is responsible to handle them.
324 344
325 :return: a tuple of the normalized algorithm tag, the algorithm 345 :return: a tuple of the normalized algorithm tag, the algorithm
326 constructor, the filename and the hex digest; 346 constructor, the filename and the hex digest;
327 if `line` cannot be parsed successfully `None` is returned 347 if `line` cannot be parsed successfully `None` is returned
328 :rtype: tuple(str, obj, str, str) or None 348 :rtype: tuple(str, obj, str, str) or None
333 # determine checkfile format (BSD or coreutils) 353 # determine checkfile format (BSD or coreutils)
334 # BSD? 354 # BSD?
335 mo = re.search(r"\A(\S+)\s*\((.*)\)\s*=\s*(.+)\n?\Z", line) 355 mo = re.search(r"\A(\S+)\s*\((.*)\)\s*=\s*(.+)\n?\Z", line)
336 if mo: 356 if mo:
337 # (tag, algorithm, filename, digest) 357 # (tag, algorithm, filename, digest)
358 if opts.allow_distinfo:
359 if mo.group(1) == "SIZE":
360 return ("SIZE", None, None, mo.group(3))
338 return (mo.group(1), 361 return (mo.group(1),
339 algotag2algotype(mo.group(1)), 362 algotag2algotype(mo.group(1)),
340 mo.group(2), 363 mo.group(2),
341 mo.group(3)) 364 mo.group(3))
342 else: 365 else:
366 if opts.allow_distinfo:
367 mo = re.search(r"\ATIMESTAMP\s*=\s*([0-9]+)\s*\n\Z", line)
368 if mo:
369 return ("TIMESTAMP", None, None, mo.group(1))
370
343 # coreutils? 371 # coreutils?
344 mo = re.search(r"([^\ ]+) [\*\ ]?(.+)\n?\Z", line) 372 mo = re.search(r"([^\ ]+) [\*\ ]?(.+)\n?\Z", line)
345 if mo: 373 if mo:
346 # (tag, algorithm, filename, digest) 374 # (tag, algorithm, filename, digest)
347 return (opts.algorithm[1], 375 return (opts.algorithm[1],