1#!/usr/bin/env python3
2import hashlib
3import json
4import os
5from pathlib import Path
6import stat
7import sys
8
9
10root = Path(sys.argv[1]).resolve(strict=True)
11if not root.is_dir():
12 raise ValueError("Expected a directory")
13digest = hashlib.sha256()
14files = 0
15size = 0
16
17
18def fail(error):
19 raise error
20
21
22for directory, children, names in os.walk(root, followlinks=False, onerror=fail):
23 children.sort()
24 for name in sorted([*children, *names]):
25 entry = Path(directory) / name
26 relative = os.fsencode(entry.relative_to(root))
27 mode = entry.lstat().st_mode
28 if stat.S_ISDIR(mode):
29 kind = b"d"
30 elif stat.S_ISLNK(mode):
31 kind = b"l"
32 elif stat.S_ISREG(mode):
33 kind = b"f"
34 else:
35 raise ValueError(f"Unsupported file type: {entry}")
36 digest.update(kind + len(relative).to_bytes(8, "big") + relative)
37 if kind == b"l":
38 target = os.fsencode(os.readlink(entry))
39 digest.update(len(target).to_bytes(8, "big") + target)
40 elif kind == b"f":
41 files += 1
42 length = entry.stat().st_size
43 size += length
44 digest.update(length.to_bytes(8, "big"))
45 with entry.open("rb") as source:
46 for chunk in iter(lambda: source.read(1024 * 1024), b""):
47 digest.update(chunk)
48print(json.dumps({"sha256": digest.hexdigest(), "files": files, "bytes": size}))