aboutsummaryrefslogtreecommitdiffstats
path: root/scripts/xattr_user_crc32
blob: 8f47bdddbd64acb44c0d60695130e663078b4701 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
#!/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()