#!/usr/bin/env python3 # Copyright 2022 vg # SPDX-License-Identifier: MIT ''' Description =========== Tag given files with their crc32 and display the tagged crc32 then the new crc32 and then the filename to stdout. Usage ===== Usage: {progname} [-z] [--] [FILENAME...] {progname} -h|--help Options: -h, --help Show the complete help -z Null instead of LF as filename separator for in and out ''' # standard modules import contextlib import functools import itertools import sys from os import getxattr, setxattr from os.path import basename # external modules import docopt # This script is not compatible below python3.7.2, always abort (not in main # since the syntax itself can cause the script to fail later inconveniently). assert sys.hexversion >= 0x03070200 def stream_split(sep='\n', stream=sys.stdin, *, rem='', chunksize=4096): if stream.isatty(): # workaround the double Ctrl-D issue for interactive entry where perf # should not be an issue thus reading 1 by 1 is ok. chunksize = 1 for data in iter(functools.partial(stream.read, chunksize), ''): splitted = ''.join((rem, data)).split(sep) for part in itertools.islice(splitted, len(splitted)-1): yield part rem = splitted[-1] yield rem def filenames(arg_filenames, separator): yield from filter(bool, arg_filenames or stream_split(separator)) def get_crc32(filename): from zlib import crc32 with open(filename, 'rb') as stream: return '%08X' % functools.reduce( lambda a, b: crc32(b, a), iter(functools.partial(stream.read, 4096), b''), 0 ) def main(): args = docopt.docopt(__doc__.format(progname=basename(sys.argv[0]))) separator = '\0' if args['-z'] else '\n' for filename in filenames(args['FILENAME'], separator): tag_crc32 = None with contextlib.suppress(OSError): tag_crc32 = getxattr(filename, 'user.crc32').decode('utf8') calculated_crc32 = get_crc32(filename) if tag_crc32 != calculated_crc32: # write access to filesystem only if needed setxattr(filename, 'user.crc32', calculated_crc32.encode('utf8')) print('%-8s %-8s %s' % (tag_crc32, calculated_crc32, filename), end=separator) main()