# HG changeset patch # User Franz Glasner # Date 1736630760 -3600 # Node ID 53614a724bf0ad6809b029ee9dcdf9e86e3b8e90 # Parent dac26a2d9de51cf43b1875d5de80472281d93cf1 Also write a (standard) CRC-32 checksum for each block of output diff -r dac26a2d9de5 -r 53614a724bf0 cutils/treesum.py --- a/cutils/treesum.py Sat Jan 11 21:18:53 2025 +0100 +++ b/cutils/treesum.py Sat Jan 11 22:26:00 2025 +0100 @@ -22,6 +22,7 @@ import stat import sys import time +import zlib from . import (__version__, __revision__) from . import util @@ -278,6 +279,7 @@ out_cm = open(opts.output, "ab") else: out_cm = open(opts.output, "wb") + out_cm = CRC32Output(out_cm) with out_cm as outfp: for d in opts.directories: @@ -321,6 +323,7 @@ """ self._outfp = outfp + self._outfp.resetdigest() self._outfp.write(format_bsd_line("VERSION", "1", None, False)) self._outfp.flush() @@ -394,9 +397,13 @@ "./@", self._use_base64)) self._outfp.flush() + self._outfp.write(format_bsd_line( + "CRC32", self._outfp.hexcrcdigest(), None, False)) return self._generate(os.path.normpath(root), tuple()) + self._outfp.write(format_bsd_line( + "CRC32", self._outfp.hexcrcdigest(), None, False)) def _generate(self, root, top): logging.debug("Handling %s/%r", root, top) @@ -522,6 +529,60 @@ return (dir_dgst.digest(), dir_size) +class CRC32Output(object): + + """Wrapper for a minimal binary file contextmanager that calculates + the CRC32 of the written bytes on the fly. + + Also acts as context manager proxy for the given context manager. + + """ + + __slots__ = ("_fp_cm", "_fp", "_crc32") + + def __init__(self, fp_cm): + self._fp_cm = fp_cm + self._fp = None + self.resetdigest() + + def __enter__(self): + assert self._fp is None + self._fp = self._fp_cm.__enter__() + return self + + def __exit__(self, *args): + rv = self._fp_cm.__exit__(*args) + self._fp = None + return rv + + def write(self, what): + self._fp.write(what) + self._crc32 = zlib.crc32(what, self._crc32) + + def flush(self): + self._fp.flush() + + def resetdigest(self): + """Reset the current CRC digest""" + self._crc32 = zlib.crc32(b"") + + def hexcrcdigest(self): + """ + + :rtype: str + + """ + return (hex(self._crc32)[2:]).upper() + + def crcdigest(self): + """ + + :rtype: int + + """ + return self._crc32 + + def normalized_compatible_mode_str(mode): # XXX FIXME: Windows and "executable" modebits = stat.S_IMODE(mode) @@ -546,7 +607,7 @@ if what == b"TIMESTAMP": assert filename is None return util.interpolate_bytes(b"TIMESTAMP = %d%s", value, ls) - if what in (b"ISOTIMESTAMP", b"FLAGS", b"VERSION"): + if what in (b"ISOTIMESTAMP", b"FLAGS", b"VERSION", b"CRC32"): assert filename is None if not isinstance(value, bytes): value = value.encode("ascii")